const { Op, where } = require("sequelize");
const sequelize = appRequire('db');
var COS = require('cos-nodejs-sdk-v5');
const { extractDominantColor } = appRequire('common/imageColor');
const { extractKeywords, matchArtType } = appRequire('common/jiebaConfig');
const tokenHelper = appRequire('common/tokenHelper');

//const unipush = appRequire('common/unipush/push');
const artPPTModel = appRequire('models/artPPT');
const collectModel = appRequire('models/collect');
const artClassModel = appRequire('models/artClass');
const orderModel = appRequire('models/order');
const downLoadModel = appRequire('models/downLoad');
const userModel = appRequire('models/user');
const brannerModel = appRequire('models/branner');

const downLoadFun = appRequire('controllers/user/downLoad');
const bgSettingModel = appRequire('models/bgSetting');
const common = appRequire('common/common');
const thisRequest = require('sync-request');
const axios = require('axios');
const fs = require('fs');
const path = require('path');

/**
 * 优化的标题提取函数
 * @param {string} filename 原始文件名
 * @returns {string} 提取的标题
 */
function extractTitle(filename) {
    if (!filename || typeof filename !== 'string') {
        console.warn('文件名无效:', filename);
        return '';
    }

    let title = filename.trim();
    console.log('🔍 原始文件名:', title);

    try {
        // 1. 处理带"、"前缀的文件名（如："1、课程名称.pptx" 或 "1、 课程名称.pptx"）
        // 匹配模式：数字 + 、（可选空格）+ 内容
        const dunhaoMatch = title.match(/^(\d+)、\s*(.+)$/);
        if (dunhaoMatch) {
            title = dunhaoMatch[2]; // 取顿号后的部分
            console.log('✂️ 去除序号前缀后:', title);
        }

        // 2. 处理书名号《》（如："《课程名称》.pptx"）
        const bookTitleMatch = title.match(/《([^》]+)》/);
        if (bookTitleMatch) {
            title = bookTitleMatch[1]; // 提取书名号内的内容
            console.log('📖 提取书名号内容:', title);
        }

        // 3. 处理文件扩展名（去除 .ppt, .pptx 等）
        title = title.replace(/\.(ppt|pptx|doc|docx|pdf)$/i, '');
        console.log('🗂️ 去除扩展名后:', title);

        // 4. 处理数字开头的标题（如："1.课程名称" 或 "01.课程名称"）
        const numberPrefixMatch = title.match(/^(\d+)\.(.+)$/);
        if (numberPrefixMatch) {
            const numberPart = numberPrefixMatch[1];
            const contentPart = numberPrefixMatch[2];

            // 如果数字部分看起来像序号（1-999），则去除
            if (parseInt(numberPart) >= 1 && parseInt(numberPart) <= 999) {
                title = contentPart;
                console.log('🔢 去除数字序号后:', title);
            }
        }

        // 5. 处理其他常见的点分隔符情况
        // 如果标题仍然包含点，且第一部分是纯数字，则去除
        if (title.includes('.')) {
            const dotParts = title.split('.');
            if (dotParts.length > 1) {
                const firstPart = dotParts[0].trim();
                // 检查第一部分是否为纯数字
                if (/^\d+$/.test(firstPart) && parseInt(firstPart) <= 999) {
                    title = dotParts.slice(1).join('.'); // 取第一个点后的所有内容
                    console.log('📍 去除数字点前缀后:', title);
                }
            }
        }

        // 6. 清理标题：去除首尾空白，去除多余的空格
        title = title.trim().replace(/\s+/g, ' ');

        // 7. 验证最终标题
        if (!title || title.length === 0) {
            console.warn('⚠️ 提取后标题为空，使用原文件名');
            // 如果提取后为空，使用原文件名（去除扩展名）
            title = filename.replace(/\.(ppt|pptx|doc|docx|pdf)$/i, '').trim();
        }

        console.log('✅ 最终标题:', title);
        return title;

    } catch (error) {
        console.error('❌ 标题提取出错:', error);
        // 出错时返回去除扩展名的原文件名
        return filename.replace(/\.(ppt|pptx|doc|docx|pdf)$/i, '').trim();
    }
}

/**
 * 从 PPTX 文件中提取所有文本内容
 * @param {string} pptPath PPTX 文件路径
 * @returns {string} 提取的所有文本内容
 */
async function extractTextFromPPT(pptPath) {
    const StreamZip = require('node-stream-zip');
    const xml2js = require('xml2js');

    let zip;
    try {
        console.log('📝 开始提取 PPT 文本内容...');

        // 检查文件是否存在
        if (!fs.existsSync(pptPath)) {
            console.warn('⚠️ PPT 文件不存在，无法提取文本');
            return '';
        }

        // 检查文件格式
        const fd = fs.openSync(pptPath, 'r');
        const firstBytes = Buffer.alloc(4);
        fs.readSync(fd, firstBytes, 0, 4, 0);
        fs.closeSync(fd);

        // ZIP文件的魔数是 "PK\x03\x04"
        const isZipFormat = firstBytes[0] === 0x50 && firstBytes[1] === 0x4B;

        if (!isZipFormat) {
            console.warn('⚠️ 不是 PPTX 格式（需要 ZIP 格式），无法提取文本');
            return '';
        }

        // 添加延迟，确保文件句柄已释放
        await new Promise(resolve => setTimeout(resolve, 500));

        // 使用 node-stream-zip 打开文件
        zip = new StreamZip.async({
            file: pptPath,
            storeEntries: true,
            skipEntryNameValidation: true
        });
        const entries = await zip.entries();

        let allText = '';
        const parser = new xml2js.Parser();

        // 遍历所有 slide 文件
        for (const entryName in entries) {
            const entry = entries[entryName];
            // 只处理 ppt/slides/slide*.xml 文件
            if (entryName.match(/ppt\/slides\/slide\d+\.xml/)) {
                try {
                    const xmlData = await zip.entryData(entryName);
                    const xmlContent = xmlData.toString('utf8');
                    const result = await parser.parseStringPromise(xmlContent);

                    // 递归提取 XML 中的所有文本节点
                    const extractTextFromNode = (node) => {
                        let text = '';

                        if (typeof node === 'string') {
                            return node;
                        }

                        if (Array.isArray(node)) {
                            for (const item of node) {
                                text += extractTextFromNode(item);
                            }
                            return text;
                        }

                        if (typeof node === 'object') {
                            // 查找 a:t 标签（文本标签）
                            if (node['a:t']) {
                                text += extractTextFromNode(node['a:t']) + ' ';
                            }

                            // 递归处理所有子节点
                            for (const key in node) {
                                if (key !== 'a:t' && typeof node[key] === 'object') {
                                    text += extractTextFromNode(node[key]);
                                }
                            }
                        }

                        return text;
                    };

                    const slideText = extractTextFromNode(result);
                    allText += slideText + '\n';

                } catch (err) {
                    console.warn(`⚠️ 解析 ${entryName} 失败:`, err.message);
                }
            }
        }

        // 关闭 ZIP 文件
        await zip.close();

        // 清理文本：去除多余空格和换行
        allText = allText.replace(/\s+/g, ' ').trim();

        // 过滤年份及其后面的标点符号（如：2006、2010/等）
        allText = allText.replace(/\b\d{4}\b[^\u4e00-\u9fa5a-zA-Z]*/g, '');

        console.log(`✅ 文本提取完成，共提取 ${allText.length} 个字符`);
        console.log(`📄 提取的文本预览: ${allText.substring(0, 200)}...`);

        return allText;

    } catch (error) {
        console.error('❌ 提取 PPT 文本时出错:', error);
        // 确保关闭 ZIP 文件
        if (zip) {
            try {
                await zip.close();
            } catch (closeError) {
                // 忽略关闭错误
            }
        }
        return '';
    }
}

// matchArtType 函数已迁移到 common/jiebaConfig.js 中

/**
 * 分析文本内容，提取教学内容和教具
 * @param {string} text PPT 中提取的文本
 * @returns {object} { desc: 教学内容描述, classDesc: 教具列表 }
 */
function analyzeTextContent(text) {
    if (!text || text.trim().length === 0) {
        return { desc: '', classDesc: '' };
    }

    console.log('🔍 开始分析文本内容...');

    let desc = '';
    let classDesc = '';

    // 1. 提取教学内容（美学内容、重难点、教学目标等）
    const descPatterns = [
        /[一二三四五六七八九十、]+\s*[美学]*内容[：:](.*?)(?=[一二三四五六七八九十、]+|$)/gs,
        /[一二三四五六七八九十、]+\s*重[难]*点[：:](.*?)(?=[一二三四五六七八九十、]+|$)/gs,
        /[一二三四五六七八九十、]+\s*教学目标[：:](.*?)(?=[一二三四五六七八九十、]+|$)/gs,
        /[一二三四五六七八九十、]+\s*活动目标[：:](.*?)(?=[一二三四五六七八九十、]+|$)/gs,
        /目标[：:](.*?)(?=[一二三四五六七八九十、]+|$)/gs,
    ];

    let descParts = [];
    for (const pattern of descPatterns) {
        const matches = text.matchAll(pattern);
        for (const match of matches) {
            if (match[1] && match[1].trim()) {
                // 获取完整的匹配内容（包括标题）
                const fullMatch = match[0].trim();
                descParts.push(fullMatch);
            }
        }
    }

    if (descParts.length > 0) {
        desc = descParts.join('；\n');
    }

    // 2. 提取教具（常见的美术用具和材料）
    const toolKeywords = [
        '卡纸', '白纸', '彩纸', '色纸', '画纸', '宣纸',
        '剪刀', '胶水', '胶带', '双面胶', '固体胶',
        '水彩笔', '马克笔', '蜡笔', '油画棒', '彩铅', '铅笔', '勾线笔', '记号笔',
        '颜料', '水粉', '水彩', '丙烯',
        '画笔', '毛笔', '排笔', '笔刷',
        '调色盘', '调色板',
        '海绵', '棉签', '抹布',
        '尺子', '直尺', '三角尺',
        '橡皮', '橡皮泥', '超轻粘土', '黏土',
        '吸管', '牙签', '棉棒',
        '纸杯', '纸盘', '纸碗',
        '绳子', '毛线', '丝带', '麻绳',
        '纽扣', '亮片', '珠子', '贴纸',
        '树叶', '树枝', '石头', '花瓣',
        '废旧材料', '卷纸芯', '塑料瓶'
    ];

    let foundTools = [];

    // 从文本中查找教具关键词
    for (const tool of toolKeywords) {
        if (text.includes(tool)) {
            foundTools.push(tool);
        }
    }

    // 使用正则提取"准备材料"、"材料"、"工具"等部分
    const toolPatterns = [
        /(?:准备)?材料[：:](.*?)(?=[一二三四五六七八九十、]+|活动[过程流程]|$)/gs,
        /工具[：:](.*?)(?=[一二三四五六七八九十、]+|活动[过程流程]|$)/gs,
        /教具[：:](.*?)(?=[一二三四五六七八九十、]+|活动[过程流程]|$)/gs,
    ];

    for (const pattern of toolPatterns) {
        const matches = text.matchAll(pattern);
        for (const match of matches) {
            if (match[1]) {
                const toolText = match[1];
                // 提取可能的工具（用顿号、逗号、分号等分隔）
                const tools = toolText.split(/[、，,；;]/);
                for (const tool of tools) {
                    const cleanTool = tool.trim();
                    if (cleanTool && cleanTool.length >= 2 && cleanTool.length <= 10) {
                        foundTools.push(cleanTool);
                    }
                }
            }
        }
    }

    // 去重
    foundTools = [...new Set(foundTools)];

    if (foundTools.length > 0) {
        classDesc = foundTools.join('、');
    }

    console.log('✅ 文本分析完成');
    console.log(`📋 教学内容: ${desc.substring(0, 100)}${desc.length > 100 ? '...' : ''}`);
    console.log(`🎨 教具: ${classDesc}`);

    return { desc, classDesc };
}

/**
 * 清空 ppt 目录下的所有 ppt/pptx 文件和 temp 目录
 */
async function clearPPTDirectory() {
    const pptDir = path.join(__dirname, 'ppt');

    try {
        console.log('🧹 开始清空 PPT 目录...');

        // 检查目录是否存在
        if (!fs.existsSync(pptDir)) {
            console.log('⚠️ PPT 目录不存在，跳过清空操作');
            return;
        }

        // 读取目录
        const files = fs.readdirSync(pptDir);
        let deletedCount = 0;

        for (const file of files) {
            const filePath = path.join(pptDir, file);
            const stat = fs.statSync(filePath);

            if (stat.isDirectory()) {
                // 删除 temp 目录
                if (file === 'temp') {
                    fs.rmSync(filePath, { recursive: true, force: true });
                    console.log(`✂️ 已删除目录: ${file}`);
                    deletedCount++;
                }
            } else if (stat.isFile()) {
                // 删除 .ppt 和 .pptx 文件
                if (file.endsWith('.ppt') || file.endsWith('.pptx')) {
                    fs.unlinkSync(filePath);
                    deletedCount++;
                }
            }
        }

        console.log(`✅ PPT 目录清空完成，共删除 ${deletedCount} 个文件/目录`);
    } catch (error) {
        console.error('❌ 清空 PPT 目录时出错:', error);
        throw error;
    }
}

// 导出对象，供其它模块调用
module.exports = {
    getBaiduFile,
    asayncBaiduFile,
    processPPTWithNodeJS, // 导出测试用
    extractTitle, // 导出标题提取函数，便于测试
    extractImagesFromPPT, // 导出图片提取函数
    selectBestImageFromExtracted, // 导出最佳图片选择函数
    calculateImageScore, // 导出评分函数
};

async function getBaiduFile(req, request) {
    let postData = req.body
    let returnData = Common.clone(Constant.DEFAULT_SUCCESS);

    let checkResult = Common.checkUrl(postData, ['path'], request);
    if (checkResult == false) return;

    // 清空 ppt 目录下的所有 ppt/pptx 文件和 temp 目录
    await clearPPTDirectory();

    // 从数据库获取百度网盘token
    const access_token = await tokenHelper.getBaiduToken();

    let baiduApi = "http://pan.baidu.com/rest/2.0/xpan/multimedia"
    let url = baiduApi + "?access_token=" + access_token + "&category=4&recursion=1&method=categorylist&parent_path=" + encodeURIComponent(postData.path) + "&ext=pptx,ppt&order=time&desc=1"

    var res = thisRequest('GET', url, {
        headers: {
            'User-Agent': 'pan.baidu.com'
        }
    });
    var body = res.getBody();
    var jsonObj = JSON.parse(body); // 解析接口返回的JSON内容

    for (let item of jsonObj.list) {
        item.size = Common.getFileSize(item.size)
    }

    returnData.data.totalNum = jsonObj.list.length
    returnData.data.dataList = jsonObj.list
    request.json(returnData)
}

async function asayncBaiduFile(req, request) {
    let postData = req.body
    let returnData = Common.clone(Constant.DEFAULT_SUCCESS);

    // 从数据库获取百度网盘token
    const access_token = await tokenHelper.getBaiduToken();

    let index = postData.startNum - 1
    let list = postData.dataList

    console.log("当前ppt:" + postData.startNum)
    let item = list[index]
    let qiuniuUrl
    let optionData = null; // 在外部声明，以便在 catch 块中访问

    // 记录上传开始时间，用于超时检测
    const uploadStartTime = Date.now();
    const maxUploadTime = 5 * 60 * 1000; // 5分钟超时

    // 超时检查函数
    const checkTimeout = () => {
        const elapsed = Date.now() - uploadStartTime;
        if (elapsed > maxUploadTime) {
            console.log(`文件 ${item.server_filename} 上传超时，已用时 ${Math.floor(elapsed / 1000)}秒`);
            returnData.msg = "上传超时，建议重试";
            returnData.status = "超时";
            return true;
        }
        return false;
    };

    // 获取专题列表（用于智能匹配）
    let artTypeList = [];
    try {
        artTypeList = await artClassModel.artType.findAll({
            where: {},
            order: [['createdAt', 'DESC']],
            raw: true
        });
        console.log(`📚 已加载 ${artTypeList.length} 个专题用于智能匹配`);
    } catch (err) {
        console.warn('⚠️ 获取专题列表失败:', err.message);
    }

    try {
        //let fileSize = Common.getFileSize(item.size)
        let fileSizeNum = Number(item.size.split("MB")[0])

        // 优化的标题提取逻辑
        item.title = extractTitle(item.server_filename);

        // 处理server_filename中的"、 "前缀（保持原逻辑兼容性）
        if (item.server_filename.indexOf("、 ") != -1) {
            item.server_filename = item.server_filename.split("、 ")[1];
        }

        console.log("标题", item.title)
        console.log("文件大小", fileSizeNum)
        if (!item.title) return console.log("标题为空")
        if (fileSizeNum > 200) {
            returnData.msg = "文件已超过200MB"
            returnData.status = "超出"
            return request.json(returnData)
        }
        //console.log(list.length, index)
        if (index == list.length) {
            deleteAllFilesInFolder("D:/work/artClassApi/controllers/admin/ppt/")
            deleteAllFilesInFolder("D:/work/artClassApi/controllers/admin/image/")
            return console.log("上传完成")
        }

        url = "http://pan.baidu.com/rest/2.0/xpan/multimedia?method=filemetas&access_token=" + access_token + "&fsids=[" + item.fs_id + "]&dlink=1&thumb=0"

        var res = thisRequest('GET', url, {
            headers: {
                'User-Agent': 'pan.baidu.com'
            }
        });
        var body = res.getBody();
        var jsonObj = JSON.parse(body); // 解析接口返回的JSON内容

        let detail = jsonObj.list[0]
        console.log("网盘下载链接", detail.dlink, item.server_filename)

        // 检查是否超时
        if (checkTimeout()) {
            return request.json(returnData);
        }

        let thisPPtUrl = await downLoadPPt(detail.dlink, item, access_token)

        let params = {
            title: item.title,
            content: item.server_filename,
            desc: item.server_filename,
            price: postData.price,
            number: '' + item.fs_id,
            status: 1,
            userId: "3h4SfJjFn9J4SHA",
            isBaiduFile: true,
            path: item.path,
            size: Common.getFileSize(detail.size),
            ageMin: 3,
            ageMax: 12,
            downLoadNum: Math.floor(Math.random() * 500) + 1
        }
        if (postData.courseId) params.courseId = postData.courseId
        if (postData.typeDesc) params.TypeId = postData.typeDesc

        optionData = {
            'desc': '',
            'sourceFrom': '',
            'tags': [],
            'zuopinPage': '1',
            'keyWords': [],
            'longImage': ''
        }

        // 检查是否超时
        if (checkTimeout()) {
            return request.json(returnData);
        }

        // 使用Node.js PPT处理方案
        optionData = await processPPTWithNodeJS(thisPPtUrl, item);
        console.log("📊 processPPTWithNodeJS返回的pageCount:", optionData.pageCount);

        // 再次检查是否超时
        if (checkTimeout()) {
            return request.json(returnData);
        }

        // 上传缩略图到腾讯云
        qiuniuUrl = await uploadImageToCOS(optionData.longImage.pageImg, item, "thumb")
        console.log("缩略图上传成功", qiuniuUrl)

        // 上传长图到腾讯云
        if (optionData.longImage && optionData.longImage.longImg) {
            let longImageUrl = await uploadImageToCOS(optionData.longImage.longImg, item, "long")
            console.log("长图上传成功", longImageUrl)
            params.longImages = [longImageUrl]
        }

        params.baiduDeskLink = await getBaiduShareLink(detail)

        // 使用优化后的关键词提取算法（基于 TF-IDF）
        const keywords = extractKeywords(item.title, 8);
        if (keywords && keywords.length > 0) {
            params.tags = keywords;
        }

        params.images = [qiuniuUrl]
        if (postData.sourceFrom) params.sourceFrom = postData.sourceFrom
        if (optionData.sourceFrom) params.sourceFrom = optionData.sourceFrom

        // 设置PPT总页数
        if (optionData.pageCount) {
            params.page = optionData.pageCount
            console.log("PPT总页数:", params.page)
        }

        let ages = getAgesFromString(item.server_filename)
        if (ages) {
            if (ages.minAge) params.ageMin = ages.minAge
            if (ages.maxAge) params.ageMax = ages.maxAge
        }
        console.log("年龄", ages)

        // 使用从 PPT 中提取的教学内容和教具
        if (optionData.extractedDesc && optionData.extractedDesc.trim()) {
            params.desc = optionData.extractedDesc;
            console.log("✅ 使用提取的教学内容作为描述");
        }
        if (optionData.extractedClassDesc && optionData.extractedClassDesc.trim()) {
            params.classDesc = optionData.extractedClassDesc;
            console.log("✅ 使用提取的教具:", optionData.extractedClassDesc);
        }

        // 智能匹配专题（如果没有手动指定专题）
        if (!params.TypeId && artTypeList.length > 0) {
            console.log('\n🎯 开始自动匹配专题...');
            const pptText = optionData.pptText || '';
            const matchedTypeId = matchArtType(item.title, pptText, artTypeList);

            if (matchedTypeId) {
                params.TypeId = matchedTypeId;
                console.log(`✅ 自动关联专题ID: ${matchedTypeId}`);
            } else {
                console.log('ℹ️ 未找到合适的专题，保持不关联');
            }
        } else if (params.TypeId) {
            console.log(`ℹ️ 已手动指定专题ID: ${params.TypeId}，跳过自动匹配`);
        }

        // 提取图片主色调（双色调）
        if (qiuniuUrl) {
            console.log('\n🎨 开始提取图片主色调...');
            try {
                const colorResult = await extractDominantColor(qiuniuUrl);
                if (colorResult && colorResult.color1) {
                    params.dominantColor = colorResult.color1;
                    if (colorResult.color2) {
                        params.dominantColor2 = colorResult.color2;
                    }
                    console.log(`✅ 双色调提取成功: ${colorResult.color1} + ${colorResult.color2 || '无'}`);
                } else {
                    console.log('⚠️ 主色调提取失败，使用默认值');
                }
            } catch (error) {
                console.error('❌ 主色调提取出错:', error.message);
            }
        }

        const artPpt = await artPPTModel.findOne({
            where: {
                number: item.fs_id
            }
        });

        if (artPpt) {
            // 如果是更新现有PPT，直接更新
            let result = await artPPTModel.update(params, {
                where: {
                    number: item.fs_id
                }
            })
            console.log("修改完成")
        } else {
            // 新上传的PPT，正常上传
            params.objectId = sid.uuid()
            let result = await artPPTModel.create(params)
            console.log("上传完成")
        }

        // 所有操作完成后，安全清理临时文件
        if (optionData.tempDir && fs.existsSync(optionData.tempDir)) {
            try {
                console.log("开始清理临时文件夹:", optionData.tempDir);
                deleteFolderRecursive(optionData.tempDir);
                console.log("临时文件夹清理完成:", optionData.tempDir);
            } catch (cleanupError) {
                console.error("清理临时文件失败:", cleanupError);
            }
        }

        returnData.msg = "上传完成"
        returnData.status = "成功"
        returnData.startNum = postData.startNum
        return request.json(returnData)
    } catch (error) {
        console.error(error); // 处理错误情况

        // 错误情况下也要清理临时文件
        if (optionData && optionData.tempDir && fs.existsSync(optionData.tempDir)) {
            try {
                console.log("错误情况下清理临时文件夹:", optionData.tempDir);
                deleteFolderRecursive(optionData.tempDir);
                console.log("错误清理完成:", optionData.tempDir);
            } catch (cleanupError) {
                console.error("错误清理临时文件失败:", cleanupError);
            }
        }

        returnData.msg = error
        returnData.status = "失败"
        returnData.startNum = postData.startNum
        return request.json(returnData)
    }
}

function deleteAllFilesInFolder(folderPath) {
    fs.readdirSync(folderPath).forEach(file => {
        const filePath = path.join(folderPath, file);
        fs.unlinkSync(filePath);
    });
}

async function getBaiduShareLink(detail) {
    return new Promise((resolve, reject) => {
        url = "https://pan.baidu.com/share/set?channel=chunlei&bdstoken=66181d98ed8e6ea5ad86a479aa326394&clienttype=0&app_id=250528&web=1&dp-logid=84335700593288560063&fid_list=[" + detail.fs_id + "]&schannel=4&pwd=1090&period=0"
        var res = thisRequest('POST', url, {
            json: {},
            headers: {
                'User-Agent': 'pan.baidu.com',
                'Content-Type': 'application/x-www-form-urlencoded',
                'Cookie': 'PSTM=1694525896; BIDUPSID=E4A24F4404CF840571A57B262C17417E; newlogin=1; MCITY=-%3A; BAIDUID=DB478FAA1B2129DEAC57A1DC61E28A3A:FG=1; H_WISE_SIDS_BFESS=40080_40302_40373_40416_40467_40458_40317_39662_40487_40512_40446_60044_60026_60046; H_PS_PSSID=40080_40302_40373_40416_40467_40458_39662_40487_40512_40446_60044_60026_60046_40510; H_WISE_SIDS=40080_40302_40373_40416_40467_40458_39662_40487_40512_40446_60044_60026_60046_40510; BAIDUID_BFESS=DB478FAA1B2129DEAC57A1DC61E28A3A:FG=1; BA_HECTOR=2k21a585840g05008025a12kh52f931j0pie31s; ZFY=3zDpG8KCfNM7VsfmtIOE8FwFn:AzZH1tUqperf6FYtrA:C; BDORZ=B490B5EBF6F3CD402E515D22BCDA1598; BDRCVFR[feWj1Vr5u3D]=I67x6TjHwwYf0; PSINO=5; delPer=0; csrfToken=Ws4nCB63RuZ6kF8rlq3DcCt1; BCLID=7818468409565482149; BCLID_BFESS=7818468409565482149; BDSFRCVID=LdFOJexroG3hp5OtBLBuqMXc1yNbUdrTDYrEOwXPsp3LGJLVYE-bEG0Pts1-dEub6j30ogKKL2OTHRKF_2uxOjjg8UtVJeC6EG0Ptf8g0M5; BDSFRCVID_BFESS=LdFOJexroG3hp5OtBLBuqMXc1yNbUdrTDYrEOwXPsp3LGJLVYE-bEG0Pts1-dEub6j30ogKKL2OTHRKF_2uxOjjg8UtVJeC6EG0Ptf8g0M5; H_BDCLCKID_SF=tbC8VCDKJKD3qbjkq45HMt00qxby26nktRR9aJ5y-J7nhnnxjfnMLxAYQqQuXPtDMmoa-Rb-QpbZql5FQP-53R0h0PJkWp5k34jaKl0MLPb5hj6gQJoDj4TyDMnMBMnrteOnaUoc3fAKftnOM46JehL3346-35543bRTLnLy5KJYMDFRDjuBDj5WDHRabK6aKC5bL6rJabC3M56oXU6q2bDeQNb3bTQ9bgrr5DQhBxnjhtT8-lofKl0vWq54WbbvLT7johRTWqR4oqKz-xonDh83KNLLKUQtKJcBoKJO5hvv8KoO3M7CyUKmDloOW-TB5bbPLUQF5l8-sq0x0bOte-bQXH_EJ5_8fRCq_KDQKt8_HRjYbb__-P4DePvb0fRZ56bHWh0b2Pth8PbtXM5hKfuR-NQ8WtjP5TrnKUT-3l7boMJRK5bdQUIT3Rr4e4j43bRTLP8hHRbpfJ_CM6-2hP-UyPkHWh37Le3lMKoaMp78jR093JO4y4Ldj4oxJpOJ5JbMopCafJOKHICCD5--efK; H_BDCLCKID_SF_BFESS=tbC8VCDKJKD3qbjkq45HMt00qxby26nktRR9aJ5y-J7nhnnxjfnMLxAYQqQuXPtDMmoa-Rb-QpbZql5FQP-53R0h0PJkWp5k34jaKl0MLPb5hj6gQJoDj4TyDMnMBMnrteOnaUoc3fAKftnOM46JehL3346-35543bRTLnLy5KJYMDFRDjuBDj5WDHRabK6aKC5bL6rJabC3M56oXU6q2bDeQNb3bTQ9bgrr5DQhBxnjhtT8-lofKl0vWq54WbbvLT7johRTWqR4oqKz-xonDh83KNLLKUQtKJcBoKJO5hvv8KoO3M7CyUKmDloOW-TB5bbPLUQF5l8-sq0x0bOte-bQXH_EJ5_8fRCq_KDQKt8_HRjYbb__-P4DePvb0fRZ56bHWh0b2Pth8PbtXM5hKfuR-NQ8WtjP5TrnKUT-3l7boMJRK5bdQUIT3Rr4e4j43bRTLP8hHRbpfJ_CM6-2hP-UyPkHWh37Le3lMKoaMp78jR093JO4y4Ldj4oxJpOJ5JbMopCafJOKHICCD5--efK; BDUSS=1kVURYZjV1bVRTc0ItNlNrMXY2SExuYVZ5MG1TUXJ0QUhlcTlqUDJMRFdWelJtSVFBQUFBJCQAAAAAAAAAAAEAAAB0NWk6w8DK9dTG1vrK1gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANbKDGbWygxmMS; BDUSS_BFESS=1kVURYZjV1bVRTc0ItNlNrMXY2SExuYVZ5MG1TUXJ0QUhlcTlqUDJMRFdWelJtSVFBQUFBJCQAAAAAAAAAAAEAAAB0NWk6w8DK9dTG1vrK1gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANbKDGbWygxmMS; STOKEN=dec913271d5214aa14994c75f96057369bf692947c0502ba85363176b4179280; PANPSC=947583313184833757%3AeawLHinWIlcNeQWQbcEwLDuv496EomYG1FE2r4%2FpjD8UwfIRgp4izRPcINtgosvZN2Uay4MICzwI5%2BB0eenXj%2BV7bvtUZ11czdySOZWPjmesggl3FQjg%2F%2Ft%2FdbyyuO9HHfneieN8l3ltK1%2FOGTUYuPh3km418maVrsewSRw5WD2QvaoB4mrv5Wu1V%2BX2JsRn4i2i1JdCg%2BTLwWQgyKHjDKp9ytS1gczJAQwoaZBLaos%3D; ndut_fmt=516D1AC19142EB6E96DF3C3D1329E10BAF83D869C1B68F73C63D376A8D253AA6; ab_sr=1.0.1_YmY4OWJlYzkzMGMzNDgwM2IyYzdlYTllM2NhY2U4NTVmNzExNWIwM2RmMjQyNjc3OGRkOWQ0MjU0MjUwNjQxMGMzOGM0MjgyMDBkOWYyZjMxMjdiZjU5YWU1YjE5MTAzYWMwMDViZWEzNDNjNDhhM2Y0YjUyZGY5ZGMyNTU1YWM5MTU0OTBiNDczZDFmZjU2NWEyMTk4NzVkMDNmNDhhNDU1MzIxYTZlNjUyY2JhMmE5Y2JhNGZhYmI0YmUzMWYw'
            }
        });
        var body = res.getBody();
        var jsonObj = JSON.parse(body); // 解析接口返回的JSON内容

        console.log("获取百度网盘链接", jsonObj)

        if (jsonObj) {
            jsonObj.baiduDeskLink = jsonObj.link
            resolve(jsonObj.link)
        }
    })
}

// 优化的分片并发下载函数
async function downLoadPPt(url, item, access_token) {
    const imagePath = path.join(__dirname, `ppt/${item.fs_id}.pptx`);

    // 检查文件缓存
    if (fs.existsSync(imagePath)) {
        const stats = fs.statSync(imagePath);
        if (stats.size > 0) {
            console.log(`[缓存命中] PPT文件已存在: ${item.fs_id}.pptx (${(stats.size / 1024 / 1024).toFixed(2)} MB)`);
            return imagePath;
        }
    }

    console.log(`[下载开始] PPT文件: ${item.fs_id}.pptx - 使用优化下载策略`);

    try {
        // 先尝试获取文件大小，判断是否支持Range请求
        const headResponse = await axios.head(url + "&access_token=" + access_token);
        const totalSize = parseInt(headResponse.headers['content-length'], 10);
        const acceptRanges = headResponse.headers['accept-ranges'];

        // 如果不支持Range请求或文件太小，使用原始方法
        if (!acceptRanges || acceptRanges !== 'bytes' || !totalSize || totalSize < 5 * 1024 * 1024) {
            console.log(`[下载策略] 文件较小或不支持分片，使用单线程下载`);
            return await downLoadPPtOriginal(url, item, access_token);
        }

        console.log(`[文件信息] 总大小: ${(totalSize / 1024 / 1024).toFixed(2)} MB，支持分片下载`);

        // 动态分片配置（优化：使用更大的分片减少请求次数）
        const optimalChunkSize = totalSize > 100 * 1024 * 1024 ? 5 * 1024 * 1024 : // 大于100MB用5MB分片
            totalSize > 50 * 1024 * 1024 ? 4 * 1024 * 1024 : // 50-100MB用4MB分片
                totalSize > 20 * 1024 * 1024 ? 3 * 1024 * 1024 : // 20-50MB用3MB分片
                    2 * 1024 * 1024; // 小于20MB用2MB分片
        const chunks = Math.ceil(totalSize / optimalChunkSize);
        let currentConcurrent = Math.min(6, chunks); // 初始6个并发（优化：提高初始并发数）
        const maxConcurrent = Math.min(10, chunks); // 最多10个并发（优化：提高最大并发数）

        console.log(`[下载策略 🚀] 分${chunks}片下载，每片${(optimalChunkSize / 1024 / 1024).toFixed(1)}MB，初始${currentConcurrent}线程并发`);

        // 创建临时目录
        const tempDir = path.join(__dirname, `ppt/temp/${item.fs_id}`);
        if (!fs.existsSync(tempDir)) {
            fs.mkdirSync(tempDir, { recursive: true });
        }

        // 下载进度和速度跟踪
        const chunkProgress = new Array(chunks).fill(0);
        const chunkSpeeds = new Array(chunks).fill(0);
        const chunkStartTime = new Array(chunks).fill(0);
        let totalDownloaded = 0;
        let lastProgressPrint = -1;
        let recentSpeeds = []; // 记录最近的速度样本
        let slowChunks = new Set(); // 记录慢速分片

        // 下载单个分片的函数（支持取消和重试）
        const downloadChunk = async (index, forceRetry = false) => {
            const start = index * optimalChunkSize;
            const end = Math.min(start + optimalChunkSize - 1, totalSize - 1);
            const chunkPath = path.join(tempDir, `chunk_${index}`);

            // 如果分片已存在且大小正确，跳过下载（除非强制重试）
            if (!forceRetry && fs.existsSync(chunkPath)) {
                const chunkStats = fs.statSync(chunkPath);
                const expectedSize = end - start + 1;
                if (chunkStats.size === expectedSize) {
                    chunkProgress[index] = expectedSize;
                    console.log(`[分片缓存] 第${index + 1}/${chunks}片已存在，跳过下载`);
                    return;
                }
            }

            // 如果是强制重试，删除原有的不完整文件
            if (forceRetry && fs.existsSync(chunkPath)) {
                fs.unlinkSync(chunkPath);
                console.log(`[强制重试] 删除分片${index + 1}的不完整文件，重新下载`);
            }

            let retries = 3;
            let chunkDownloaded = 0;
            chunkStartTime[index] = Date.now();
            let abortController = null; // 用于取消请求

            while (retries > 0) {
                try {
                    // 优化：动态计算超时时间（假设最低速度100KB/s）
                    const chunkSize = end - start + 1;
                    const minSpeed = 100 * 1024; // 100KB/s 最低速度
                    const baseTimeout = Math.max(15000, (chunkSize / minSpeed) * 1000);
                    const timeoutMs = slowChunks.has(index) ? baseTimeout * 0.8 : baseTimeout;

                    const response = await axios({
                        url: url + "&access_token=" + access_token,
                        method: 'GET',
                        responseType: 'stream',
                        headers: {
                            'Range': `bytes=${start}-${end}`,
                            'Connection': 'keep-alive',  // 优化：保持连接
                            'Accept-Encoding': 'gzip, deflate'  // 优化：支持压缩
                        },
                        timeout: timeoutMs,
                        maxRedirects: 5,
                        // 优化：HTTP连接池配置
                        httpAgent: new (require('http').Agent)({
                            keepAlive: true,
                            keepAliveMsecs: 30000,
                            maxSockets: 15,
                            maxFreeSockets: 10,
                            timeout: timeoutMs
                        }),
                        httpsAgent: new (require('https').Agent)({
                            keepAlive: true,
                            keepAliveMsecs: 30000,
                            maxSockets: 15,
                            maxFreeSockets: 10,
                            timeout: timeoutMs
                        })
                    });

                    const writer = fs.createWriteStream(chunkPath);
                    let chunkInterval;
                    let shouldCancel = false; // 是否应该取消当前分片

                    // 监控分片下载速度和超时
                    let hasWarnedSlow = false;
                    let stuckCount = 0; // 卡住计数
                    let lastDownloaded = 0; // 上次检查时的下载量

                    const monitorChunkSpeed = () => {
                        if (chunkDownloaded > 0) {
                            const elapsed = (Date.now() - chunkStartTime[index]) / 1000;
                            const currentSpeed = chunkDownloaded / 1024 / 1024 / elapsed;
                            chunkSpeeds[index] = currentSpeed;

                            // 优化：更快的卡死检测（3秒内没有新数据）
                            if (chunkDownloaded === lastDownloaded) {
                                stuckCount++;
                                if (stuckCount >= 2) { // 优化：连续2次检查都没有进度（6秒）
                                    console.log(`[卡死检测] 分片${index + 1}已6秒无响应，准备取消重试`);
                                    shouldCancel = true;
                                    response.data.destroy(); // 销毁流
                                    return;
                                }
                            } else {
                                stuckCount = 0; // 重置卡住计数
                                lastDownloaded = chunkDownloaded;
                            }

                            // 优化：更激进的慢速检测和取消策略
                            if (currentSpeed < 0.15 && elapsed > 3 && !hasWarnedSlow) {
                                slowChunks.add(index);
                                hasWarnedSlow = true;
                                console.log(`[性能警告] 分片${index + 1}速度过慢: ${currentSpeed.toFixed(2)} MB/s`);

                                // 优化：极慢的分片更快取消（速度低于0.05MB/s且已下载超过8秒）
                                if (currentSpeed < 0.05 && elapsed > 8) {
                                    console.log(`[性能决策] 分片${index + 1}速度极慢(${currentSpeed.toFixed(3)} MB/s)，立即取消重试`);
                                    shouldCancel = true;
                                    response.data.destroy(); // 销毁流
                                    return;
                                }
                            }
                        } else if ((Date.now() - chunkStartTime[index]) > 10000) {
                            // 10秒还没开始下载，直接取消
                            console.log(`[超时检测] 分片${index + 1}超过10秒未开始，取消重试`);
                            shouldCancel = true;
                            response.data.destroy();
                        }
                    };

                    // 优化：每3秒检查一次分片速度（更频繁的监控）
                    chunkInterval = setInterval(monitorChunkSpeed, 3000);

                    response.data.on('data', (data) => {
                        chunkDownloaded += data.length;
                        chunkProgress[index] = chunkDownloaded;
                        totalDownloaded = chunkProgress.reduce((a, b) => a + b, 0);

                        const progress = Math.floor((totalDownloaded / totalSize) * 100);
                        const downloadedMB = (totalDownloaded / 1024 / 1024).toFixed(2);
                        const totalMB = (totalSize / 1024 / 1024).toFixed(2);

                        // 计算实时速度
                        const elapsed = (Date.now() - startTime) / 1000;
                        const currentSpeed = totalDownloaded / 1024 / 1024 / elapsed;

                        // 记录速度样本用于分析
                        recentSpeeds.push(currentSpeed);
                        if (recentSpeeds.length > 10) {
                            recentSpeeds.shift();
                        }

                        // 优化：更积极的动态并发调整
                        if (recentSpeeds.length >= 3 && progress % 5 === 0) { // 优化：更频繁调整（5%而非10%）
                            const avgSpeed = recentSpeeds.reduce((a, b) => a + b, 0) / recentSpeeds.length;
                            const lastSpeed = recentSpeeds[recentSpeeds.length - 1];

                            // 如果速度持续下降，减少并发
                            if (lastSpeed < avgSpeed * 0.5 && currentConcurrent > 3) {
                                const oldConcurrent = currentConcurrent;
                                currentConcurrent = Math.max(3, currentConcurrent - 1);
                                if (oldConcurrent !== currentConcurrent) {
                                    console.log(`[并发调整 ⬇️] 检测到速度下降(${lastSpeed.toFixed(2)}MB/s)，并发数从${oldConcurrent}降至${currentConcurrent}`);
                                }
                            }
                            // 优化：更容易增加并发（条件放宽）
                            else if (lastSpeed > avgSpeed * 1.1 && currentConcurrent < maxConcurrent && slowChunks.size < 3) {
                                const oldConcurrent = currentConcurrent;
                                currentConcurrent = Math.min(maxConcurrent, currentConcurrent + 2); // 优化：每次增加2个
                                if (oldConcurrent !== currentConcurrent) {
                                    console.log(`[并发调整 ⬆️] 速度良好(${lastSpeed.toFixed(2)}MB/s)，并发数从${oldConcurrent}升至${currentConcurrent}`);
                                }
                            }
                        }

                        if (progress % 5 === 0 && progress !== lastProgressPrint && progress > 0) {
                            const activeChunks = chunkSpeeds.filter(s => s > 0).length;
                            console.log(`[下载进度] ${progress}% (${downloadedMB}/${totalMB} MB) - 速度: ${currentSpeed.toFixed(2)} MB/s - 活跃线程: ${activeChunks}`);
                            lastProgressPrint = progress;
                        }
                    });

                    response.data.pipe(writer);

                    await new Promise((resolve, reject) => {
                        writer.on('finish', () => {
                            clearInterval(chunkInterval);
                            chunkSpeeds[index] = 0; // 完成后清零速度

                            // 如果是因为取消而结束，抛出错误重试
                            if (shouldCancel) {
                                reject(new Error(`分片${index + 1}被取消（速度过慢或卡死）`));
                            } else {
                                resolve();
                            }
                        });

                        writer.on('error', (err) => {
                            clearInterval(chunkInterval);
                            reject(err);
                        });

                        // 定期检查是否应该取消
                        const cancelChecker = setInterval(() => {
                            if (shouldCancel) {
                                clearInterval(cancelChecker);
                                clearInterval(chunkInterval);
                                writer.destroy();
                                reject(new Error(`分片${index + 1}因速度过慢被主动取消`));
                            }
                        }, 1000);
                    });

                    // 只有真正完成才退出重试循环
                    if (!shouldCancel) {
                        break;
                    }

                } catch (error) {
                    retries--;

                    // 如果是主动取消的，重置下载进度
                    if (error.message.includes('被取消') || error.message.includes('被主动取消')) {
                        chunkProgress[index] = 0;
                        chunkDownloaded = 0;
                        console.log(`[主动重试] 分片${index + 1}被取消，准备重新下载，剩余重试次数: ${retries}`);

                        // 被取消的分片，短暂等待后立即重试
                        await new Promise(resolve => setTimeout(resolve, 1000));

                        // 如果还有重试机会，从慢速列表移除，给它一个新机会
                        if (retries > 0) {
                            slowChunks.delete(index);
                        }
                    } else {
                        // 其他错误
                        slowChunks.add(index);

                        if (retries === 0) {
                            console.error(`[分片失败] 分片${index + 1}彻底失败: ${error.message}`);
                            throw error;
                        }

                        // 优化：减少重试等待时间，快速失败快速重试
                        const backoffTime = (3 - retries) * 1000; // 优化退避: 1s, 2s, 3s
                        console.log(`[重试] 分片${index + 1}失败(${error.message})，${backoffTime / 1000}秒后重试，剩余次数: ${retries}`);
                        await new Promise(resolve => setTimeout(resolve, backoffTime));
                    }
                }
            }
        };

        const startTime = Date.now();

        // 使用优化的队列控制并发
        const normalQueue = Array.from({ length: chunks }, (_, i) => i);
        const retryQueue = []; // 失败重试队列
        const running = [];
        let completedChunks = 0;

        while ((normalQueue.length > 0 || retryQueue.length > 0 || running.length > 0) && completedChunks < chunks) {
            // 优先处理正常队列，然后处理重试队列
            const queue = normalQueue.length > 0 ? normalQueue : retryQueue;

            // 根据当前动态并发数填充运行队列
            while (running.length < currentConcurrent && queue.length > 0) {
                const index = queue.shift();
                const isRetry = queue === retryQueue;

                if (isRetry) {
                    console.log(`[重试队列] 开始重试分片${index + 1}`);
                }

                const promise = downloadChunk(index)
                    .then(() => {
                        completedChunks++;
                        // 只在关键节点输出完成信息
                        if (completedChunks === 1 || completedChunks === chunks || completedChunks % 5 === 0) {
                            console.log(`[分片完成] ${completedChunks}/${chunks} (${(completedChunks / chunks * 100).toFixed(0)}%)`);
                        }
                        const idx = running.indexOf(promise);
                        if (idx !== -1) running.splice(idx, 1);
                    })
                    .catch(error => {
                        console.error(`[分片错误] 分片${index + 1}失败: ${error.message}`);
                        const idx = running.indexOf(promise);
                        if (idx !== -1) running.splice(idx, 1);

                        // 如果不是重试队列来的，加入重试队列
                        if (!isRetry && retryQueue.length < 5) {
                            retryQueue.push(index);
                            console.log(`[重试队列] 分片${index + 1}加入重试队列`);
                        } else {
                            // 重试也失败了，或重试队列已满，降级处理
                            throw new Error(`分片${index + 1}下载彻底失败`);
                        }
                    });
                running.push(promise);
            }

            // 等待任意一个任务完成
            if (running.length > 0) {
                await Promise.race(running);

                // 定期输出状态
                const elapsed = (Date.now() - startTime) / 1000;
                if (elapsed > 0 && Math.floor(elapsed) % 10 === 0) {
                    const avgSpeed = (totalDownloaded / 1024 / 1024 / elapsed).toFixed(2);
                    console.log(`[状态汇总] 完成: ${completedChunks}/${chunks}, 队列: ${normalQueue.length}, 重试: ${retryQueue.length}, 运行: ${running.length}, 平均速度: ${avgSpeed} MB/s`);
                }
            }
        }

        // 检查是否所有分片都下载完成
        if (completedChunks < chunks) {
            throw new Error(`只完成了${completedChunks}/${chunks}个分片，下载不完整`);
        }

        console.log(`[合并文件] 正在合并${chunks}个分片...`);

        // 合并所有分片
        const writeStream = fs.createWriteStream(imagePath);

        for (let i = 0; i < chunks; i++) {
            const chunkPath = path.join(tempDir, `chunk_${i}`);
            if (!fs.existsSync(chunkPath)) {
                throw new Error(`分片${i}不存在，合并失败`);
            }
            const data = fs.readFileSync(chunkPath);
            writeStream.write(data);
            fs.unlinkSync(chunkPath); // 删除临时文件
        }

        // 关闭文件流
        writeStream.end();

        // 等待文件流完全关闭
        await new Promise((resolve, reject) => {
            writeStream.on('finish', () => {
                console.log('✅ 文件流已关闭');
                resolve();
            });
            writeStream.on('error', reject);
        });

        // 额外等待确保操作系统完成写入
        console.log('⏳ 等待文件系统完成写入（3秒）...');
        await new Promise(resolve => setTimeout(resolve, 3000));

        // 验证合并后的文件
        const mergedSize = fs.statSync(imagePath).size;
        console.log(`📊 合并文件大小验证: ${(mergedSize / 1024 / 1024).toFixed(2)} MB (预期: ${(totalSize / 1024 / 1024).toFixed(2)} MB)`);

        if (Math.abs(mergedSize - totalSize) > 1024) {
            throw new Error(`文件大小不匹配: 实际${mergedSize}字节, 预期${totalSize}字节`);
        }

        // 验证 ZIP 文件头
        const fd = fs.openSync(imagePath, 'r');
        const header = Buffer.alloc(4);
        fs.readSync(fd, header, 0, 4, 0);
        fs.closeSync(fd);

        const isValidZip = header[0] === 0x50 && header[1] === 0x4B;
        if (!isValidZip) {
            throw new Error('合并的文件不是有效的 ZIP 格式');
        }

        console.log('✅ ZIP 文件头验证通过');

        // 深度验证 ZIP 文件完整性（尝试打开并读取结构）
        console.log('🔍 正在验证 ZIP 文件完整性...');
        const StreamZip = require('node-stream-zip');
        let testZip = null;
        try {
            testZip = new StreamZip.async({
                file: imagePath,
                storeEntries: true,
                skipEntryNameValidation: true
            });

            const entries = await testZip.entries();
            const entryCount = Object.keys(entries).length;
            console.log(`✅ ZIP 文件完整性验证通过，包含 ${entryCount} 个条目`);

            await testZip.close();
        } catch (zipError) {
            if (testZip) {
                try {
                    await testZip.close();
                } catch (closeError) {
                    console.warn('⚠️ 关闭测试ZIP失败:', closeError.message);
                }
            }
            throw new Error(`ZIP 文件损坏或不完整: ${zipError.message}`);
        }

        // 清理临时目录
        try {
            fs.rmdirSync(tempDir, { recursive: true });
            console.log('🗑️ 临时文件已清理');
        } catch (cleanError) {
            console.warn('⚠️ 临时文件清理失败:', cleanError.message);
        }

        const elapsed = ((Date.now() - startTime) / 1000).toFixed(2);
        const speed = (totalSize / 1024 / 1024 / elapsed).toFixed(2);
        console.log(`[下载完成] PPT文件已保存: ${imagePath} (${(totalSize / 1024 / 1024).toFixed(2)} MB)`);
        console.log(`[下载统计] 耗时: ${elapsed}秒，平均速度: ${speed} MB/s`);

        return imagePath;

    } catch (error) {
        console.error(`[优化失败] 分片下载失败，降级到单线程: ${error.message}`);
        return await downLoadPPtOriginal(url, item, access_token);
    }
}

// 原始单线程下载方法（作为降级方案）
async function downLoadPPtOriginal(url, item, access_token) {
    const imagePath = path.join(__dirname, `ppt/${item.fs_id}.pptx`);
    const writer = fs.createWriteStream(imagePath);

    console.log(`[单线程下载] PPT文件: ${item.fs_id}.pptx`);

    const response = await axios({
        url: url + "&access_token=" + access_token,
        method: 'GET',
        responseType: 'stream',
        timeout: 60000 // 60秒超时
    });

    const totalSize = parseInt(response.headers['content-length'], 10);
    let downloadedSize = 0;
    let lastProgressPrint = -1;
    const startTime = Date.now();

    if (totalSize) {
        console.log(`[文件信息] 总大小: ${(totalSize / 1024 / 1024).toFixed(2)} MB`);
    } else {
        console.log(`[文件信息] 文件大小未知，开始下载...`);
    }

    response.data.on('data', (chunk) => {
        downloadedSize += chunk.length;

        if (totalSize) {
            const progress = Math.floor((downloadedSize / totalSize) * 100);
            const downloadedMB = (downloadedSize / 1024 / 1024).toFixed(2);
            const totalMB = (totalSize / 1024 / 1024).toFixed(2);
            const elapsed = (Date.now() - startTime) / 1000;
            const speed = elapsed > 0 ? (downloadedSize / 1024 / 1024 / elapsed).toFixed(2) : 0;

            if (progress % 10 === 0 && progress !== lastProgressPrint && progress > 0) {
                console.log(`[下载进度] ${progress}% (${downloadedMB}/${totalMB} MB) - 速度: ${speed} MB/s`);
                lastProgressPrint = progress;
            }
        } else {
            const downloadedMB = (downloadedSize / 1024 / 1024).toFixed(2);
            if (Math.floor(downloadedMB) !== Math.floor((downloadedSize - chunk.length) / 1024 / 1024)) {
                const elapsed = (Date.now() - startTime) / 1000;
                const speed = elapsed > 0 ? (downloadedSize / 1024 / 1024 / elapsed).toFixed(2) : 0;
                console.log(`[下载进度] 已下载: ${downloadedMB} MB - 速度: ${speed} MB/s`);
            }
        }
    });

    response.data.pipe(writer);

    return new Promise((resolve, reject) => {
        writer.on('finish', async () => {
            const elapsed = ((Date.now() - startTime) / 1000).toFixed(2);
            const finalSizeMB = (downloadedSize / 1024 / 1024).toFixed(2);
            const speed = elapsed > 0 ? (downloadedSize / 1024 / 1024 / elapsed).toFixed(2) : 0;
            console.log(`[下载完成] PPT文件已保存: ${imagePath} (${finalSizeMB} MB)`);
            console.log(`[下载统计] 耗时: ${elapsed}秒，平均速度: ${speed} MB/s`);

            // 确保文件流完全关闭，等待1秒让系统完成文件写入
            console.log('⏳ 等待文件系统完成写入...');
            await new Promise(resolve => setTimeout(resolve, 1000));

            resolve(imagePath);
        });
        writer.on('error', (error) => {
            console.error(`[下载失败] PPT文件下载出错: ${error.message}`);
            reject(error);
        });
    });
}

function deleteFolderRecursive(folderPath) {
    //判断文件夹是否存在
    if (fs.existsSync(folderPath)) {
        //读取文件夹下的文件目录，以数组形式输出
        fs.readdirSync(folderPath).forEach((file) => {
            //拼接路径
            const curPath = path.join(folderPath, file);
            //判断是不是文件夹，如果是，继续递归
            if (fs.lstatSync(curPath).isDirectory()) {
                deleteFolderRecursive(curPath);
            } else {
                //删除文件或文件夹
                fs.unlinkSync(curPath);
            }
        });
        //仅可用于删除空目录
        fs.rmdirSync(folderPath);
    } else {
        console.log("目前不存在")
    }
}


function getAgesFromString(ageRange) {
    const ageRegex = /(\d+)\s*-\s*(\d+)/; // 匹配形如"10 - 20"的年龄范围
    const match = ageRegex.exec(ageRange);
    if (match) {
        return { minAge: parseInt(match[1], 10), maxAge: parseInt(match[2], 10) };
    } else {
        const ageRegex2 = /(\d+)\s*~\s*(\d+)/; // 匹配形如"10 ~ 20"的年龄范围
        const match2 = ageRegex2.exec(ageRange);
        if (match2) {
            return { minAge: parseInt(match2[1], 10), maxAge: parseInt(match2[2], 10) };
        }
    }
    return null;
}

/**
 * 智能选择最适合作为缩略图的PPT页面
 * 重点分析倒数2-5张图片，因为美术课件的作品完成效果图通常在靠后位置
 * ⚠️ 排除最后一张：最后一张不会被选为作品图，如果识别到最后一张则改为第1张
 * 通过色彩丰富度、位置权重、亮度等多维度评分，自动选择最佳页面
 * 如果识别失败，降级使用第1张作为缩略图
 * @param {Array} slides 所有幻灯片图片路径数组
 * @returns {Promise<object>} 返回最佳缩略图信息 {path: string, index: number, score: number, reversePosition: number}
 */
async function selectBestThumbnail(slides) {
    const { Jimp } = require('jimp');

    try {
        console.log(`\n🔍 开始智能识别最佳缩略图...`);
        console.log(`📊 共${slides.length}张PPT，限制从倒数第4到倒数第2页中选择`);

        // 特殊处理：如果只有1张PPT，直接返回第1张
        if (slides.length === 1) {
            console.log(`⚠️ 只有1张PPT，直接使用第1张`);
            return {
                index: 0,
                path: slides[0],
                score: 0,
                sizeScore: 0,
                pixels: 0
            };
        }

        // 🎯 限制范围：只从倒数第2到倒数第4个页面中选择
        let startIndex, endIndex;

        if (slides.length >= 4) {
            // 倒数第4个的索引是 slides.length - 4
            // 倒数第2个的索引是 slides.length - 2
            startIndex = slides.length - 4;
            endIndex = slides.length - 1; // 倒数第2个（不包含倒数第1个）
            console.log(`✨ 从倒数第4到倒数第2页中选择（第${startIndex + 1}-${endIndex}页，共${endIndex - startIndex}张）\n`);
        } else if (slides.length >= 2) {
            // 如果总页数小于4，从第一页开始到倒数第2页
            startIndex = 0;
            endIndex = slides.length - 1;
            console.log(`✨ PPT较少，从第1到倒数第2页中选择（共${endIndex}张）\n`);
        } else {
            // 只有1页的情况已在上面处理
            startIndex = 0;
            endIndex = 1;
        }

        const scores = [];

        // 分析图片（排除最后一张）
        for (let i = startIndex; i < endIndex; i++) {
            try {
                const image = await Jimp.read(slides[i]);
                const width = image.bitmap.width;
                const height = image.bitmap.height;

                // 1. 计算色彩丰富度（通过统计不同颜色的数量）
                const colorSet = new Set();
                let totalBrightness = 0;
                let pixelCount = 0;

                // 采样分析（每10个像素采样一次，提高速度）
                for (let y = 0; y < height; y += 10) {
                    for (let x = 0; x < width; x += 10) {
                        const color = image.getPixelColor(x, y);
                        colorSet.add(color);

                        // 计算亮度 (使用位运算解析RGBA值)
                        const r = (color >> 24) & 0xFF;
                        const g = (color >> 16) & 0xFF;
                        const b = (color >> 8) & 0xFF;
                        const brightness = (r + g + b) / 3;
                        totalBrightness += brightness;
                        pixelCount++;
                    }
                }

                const colorDiversity = colorSet.size; // 颜色多样性
                const avgBrightness = totalBrightness / pixelCount; // 平均亮度

                // 2. 位置权重：在限定范围内（倒数第2到倒数第4），倒数第2张权重最高（已降低权重）
                const reversePosition = slides.length - i; // 倒数位置：1, 2, 3, 4...
                let positionWeight = 0;
                if (reversePosition === 2) {
                    positionWeight = 2000; // 倒数第2张：最高权重（降低了70%）
                } else if (reversePosition === 3) {
                    positionWeight = 1500; // 倒数第3张（降低了62.5%）
                } else if (reversePosition === 4) {
                    positionWeight = 1000; // 倒数第4张（降低了66.7%）
                } else {
                    // 其他页面（当总页数<4时可能出现）
                    positionWeight = (i / slides.length) * 500;
                }

                // 3. 排除过暗或过亮的图片（可能是纯文字页面）
                let brightnessScore = 0;
                if (avgBrightness > 50 && avgBrightness < 200) {
                    brightnessScore = 1000;
                }

                // 4. 图片尺寸评分（新增：大图片权重更高）
                const pixels = width * height;
                let sizeScore = 0;
                if (pixels > 1000000) { // 大于100万像素
                    sizeScore = 2000;
                } else if (pixels > 500000) { // 50万-100万像素
                    sizeScore = 1500;
                } else if (pixels > 250000) { // 25万-50万像素
                    sizeScore = 1000;
                } else {
                    sizeScore = 500; // 较小的图片
                }

                // 5. 综合评分（颜色种类为第一权重）
                // 🎯 权重优先级：颜色种类（×5.0）> 图片尺寸（2000）> 位置（2000）> 亮度（1000）
                const score = (colorDiversity * 5.0) + sizeScore + positionWeight + brightnessScore;

                scores.push({
                    index: i,
                    path: slides[i],
                    score: score,
                    colorDiversity: colorDiversity,
                    avgBrightness: avgBrightness.toFixed(2),
                    reversePosition: reversePosition,
                    positionWeight: positionWeight,
                    sizeScore: sizeScore,
                    pixels: pixels
                });

                console.log(`第${i + 1}页 (倒数第${reversePosition}): 色彩=${colorDiversity}, 尺寸=${(pixels/10000).toFixed(1)}万像素(+${sizeScore}分), 位置权重=${positionWeight}, 总分=${score.toFixed(2)}`);

            } catch (error) {
                console.warn(`⚠️ 分析第${i + 1}页失败:`, error.message);
                // 如果分析失败，给一个较低的分数
                scores.push({
                    index: i,
                    path: slides[i],
                    score: 0,
                    colorDiversity: 0,
                    avgBrightness: 0,
                    reversePosition: slides.length - i,
                    positionWeight: 0,
                    sizeScore: 0,
                    pixels: 0
                });
            }
        }

        // 如果没有成功分析任何图片，降级使用第1张
        if (scores.length === 0) {
            console.log(`⚠️ 未能分析任何图片，使用第1张作为降级方案`);
            return {
                index: 0,
                path: slides[0],
                score: 0,
                sizeScore: 0,
                pixels: 0
            };
        }

        // 找出得分最高的页面
        let bestSlide = scores.reduce((best, current) => {
            return current.score > best.score ? current : best;
        });

        // 安全检查：如果识别结果是最后一张（不应该发生，但以防万一），强制改为第1张
        if (bestSlide.index === slides.length - 1) {
            console.log(`⚠️ 检测到选择了最后一张，强制改为第1张`);
            bestSlide = {
                index: 0,
                path: slides[0],
                score: 0,
                reversePosition: slides.length,
                colorDiversity: 0,
                avgBrightness: 0,
                positionWeight: 0,
                sizeScore: 0,
                pixels: 0
            };
        }

        console.log(`\n✅ 智能选择结果: 第${bestSlide.index + 1}页 (倒数第${bestSlide.reversePosition}) [总分: ${bestSlide.score.toFixed(2)}]`);
        console.log(`   🎨 色彩丰富度: ${bestSlide.colorDiversity} (第一权重 ×5.0)`);
        console.log(`   📐 图片尺寸: ${(bestSlide.pixels/10000).toFixed(1)}万像素 (+${bestSlide.sizeScore}分)`);
        console.log(`   📍 位置权重: ${bestSlide.positionWeight}`);
        console.log(`   💡 平均亮度: ${bestSlide.avgBrightness}`);
        console.log(`   🏆 这张最可能是作品完成效果图\n`);

        return bestSlide;

    } catch (error) {
        console.error("❌ 智能选择缩略图失败，使用第1张作为降级方案:", error);
        // 如果智能选择失败，降级使用第1张
        return {
            index: 0,
            path: slides[0],
            score: 0,
            sizeScore: 0,
            pixels: 0
        };
    }
}

/**
 * Node.js PPT处理备用方案 - 优先提取PPT中的嵌入图片
 * @param {string} pptUrl PPT文件路径
 * @param {object} item 文件信息
 * @returns {object} 处理结果
 */
async function processPPTWithNodeJS(pptUrl, item) {
    let tempDir;

    try {
        console.log("开始Node.js PPT处理:", pptUrl);

        // 创建临时目录
        tempDir = path.join(__dirname, `temp_${item.fs_id}`);
        if (!fs.existsSync(tempDir)) {
            fs.mkdirSync(tempDir, { recursive: true });
        }

        // 🆕 第一步：尝试从PPT中提取嵌入的图片（作品图）
        console.log("\n🎨 步骤1: 尝试从PPT中提取嵌入的图片（作为作品图）...");
        let bestArtworkImage = null;
        let artworkImageCount = 0;

        try {
            const extractedImages = await extractImagesFromPPT(pptUrl, tempDir, item.fs_id);

            if (extractedImages && extractedImages.length > 0) {
                console.log(`✅ 成功提取${extractedImages.length}张嵌入图片`);
                artworkImageCount = extractedImages.length;

                // 智能选择最适合作为作品图的图片
                bestArtworkImage = await selectBestImageFromExtracted(extractedImages);
                console.log(`✨ 已选择图片作为作品图: ${path.basename(bestArtworkImage.path)}`);
            } else {
                console.log("⚠️ 未找到嵌入图片");
            }
        } catch (extractError) {
            console.warn("⚠️ 图片提取失败:", extractError.message);
        }

        // 📄 第二步：始终执行PPT页面转PNG（用于生成长图）
        console.log("\n📄 步骤2: 转换PPT页面为PNG（用于生成长图）...");
        const outputDir = path.join(tempDir, 'slides');
        if (!fs.existsSync(outputDir)) {
            fs.mkdirSync(outputDir, { recursive: true });
        }

        const slides = await convertPPTtoPNG(pptUrl, outputDir);
        console.log(`转换完成，共${slides.length}张幻灯片`);

        if (slides.length === 0) {
            throw new Error("PPT转换失败，没有生成图片");
        }

        // 🖼️ 第三步：确定最终的缩略图
        let thumbnailPath;
        let sourceFrom;
        let desc;

        if (bestArtworkImage) {
            // 如果成功提取到作品图，使用作品图作为缩略图
            thumbnailPath = bestArtworkImage.path;
            sourceFrom = "PPT嵌入图片提取";
            desc = `从PPT中提取了${artworkImageCount}张嵌入图片，并选择最佳作品图`;
            console.log(`✨ 使用提取的作品图作为缩略图`);
        } else {
            // 如果没有提取到作品图，使用PPT第一页作为缩略图
            const bestThumbnail = await selectBestThumbnail(slides);
            thumbnailPath = bestThumbnail.path;
            sourceFrom = "PPT页面转换";
            desc = `使用PPT第${bestThumbnail.index + 1}页作为缩略图`;
            console.log(`✨ 使用PPT第${bestThumbnail.index + 1}页作为缩略图`);
        }

        // 📋 第四步：合并PPT页面为长图
        console.log("\n📋 步骤3: 合并PPT页面为长图...");
        const longImagePath = await combineImagesToLongImage(slides, tempDir, item.fs_id);
        console.log(`✅ 长图生成完成，共拼接${slides.length}页`);

        // 📝 第五步：提取PPT中的文本内容并分析
        console.log("\n📝 步骤4: 提取PPT文本内容并分析...");
        let extractedDesc = '';
        let extractedClassDesc = '';
        let pptText = '';

        try {
            pptText = await extractTextFromPPT(pptUrl);
            if (pptText) {
                const analyzed = analyzeTextContent(pptText);
                extractedDesc = analyzed.desc;
                extractedClassDesc = analyzed.classDesc;
                console.log(`✅ 文本分析完成`);
            } else {
                console.log('⚠️ 未提取到文本内容');
            }
        } catch (textError) {
            console.warn("⚠️ 文本提取/分析失败:", textError.message);
        }

        console.log("Node.js PPT处理完成");

        return {
            longImage: {
                pageImg: thumbnailPath,
                longImg: longImagePath
            },
            sourceFrom: sourceFrom,
            desc: desc,
            extractedDesc: extractedDesc,  // 新增：提取的教学内容
            extractedClassDesc: extractedClassDesc,  // 新增：提取的教具
            pptText: pptText,  // 新增：提取的完整文本（用于专题匹配）
            pageCount: slides.length,  // 新增：PPT的总页数
            tempDir: tempDir
        };

    } catch (error) {
        console.error("Node.js PPT处理失败:", error);
        // 清理临时文件
        if (tempDir && fs.existsSync(tempDir)) {
            try {
                deleteFolderRecursive(tempDir);
                console.log("已清理临时文件夹:", tempDir);
            } catch (cleanupError) {
                console.error("清理临时文件失败:", cleanupError);
            }
        }
        throw error;
    }
}

/**
 * 使用LibreOffice ppt-png将PPT转换为PNG图片
 * @param {string} pptPath PPT文件路径
 * @param {string} outputDir 输出目录
 * @returns {Array} 生成的图片路径数组
 */
async function convertPPTtoPNG(pptPath, outputDir) {
    try {
        console.log('开始PPT转PNG，输入文件:', pptPath);
        console.log('输出目录:', outputDir);

        // 使用LibreOffice ppt-png方案
        return await convertPPTviaDirectPNG(pptPath, outputDir);

    } catch (error) {
        console.error('PPT转换失败:', error);
        throw error;
    }
}

/**
 * 使用LibreOffice命令行直接转换PPT（需要LibreOffice）
 * @param {string} pptPath PPT文件路径
 * @param {string} outputDir 输出目录
 * @returns {Array} 生成的图片路径数组
 */
async function convertPPTviaDirectPNG(pptPath, outputDir) {
    console.log('使用LibreOffice命令行直接转换方案');
    console.log('PPT文件路径:', pptPath);
    console.log('输出目录:', outputDir);

    // 检查PPT文件是否存在
    if (!fs.existsSync(pptPath)) {
        throw new Error(`PPT文件不存在: ${pptPath}`);
    }

    // 检查输出目录是否存在，不存在则创建
    if (!fs.existsSync(outputDir)) {
        fs.mkdirSync(outputDir, { recursive: true });
        console.log('创建输出目录:', outputDir);
    }

    // 检查依赖工具是否可用
    const { exec } = require('child_process');

    // 检查LibreOffice
    try {
        await new Promise((resolve, reject) => {
            exec('libreoffice --version', (error, stdout, stderr) => {
                if (error) {
                    console.error('LibreOffice检查失败:', error.message);
                    reject(new Error('LibreOffice未安装或不在PATH中。请运行: brew install libreoffice'));
                } else {
                    console.log('LibreOffice版本:', stdout.trim());
                    resolve();
                }
            });
        });
    } catch (libreOfficeError) {
        throw libreOfficeError;
    }

    // 检查ImageMagick
    try {
        await new Promise((resolve, reject) => {
            exec('magick --version', (error, stdout, stderr) => {
                if (error) {
                    console.error('ImageMagick检查失败:', error.message);
                    reject(new Error('ImageMagick未安装或不在PATH中。请运行: brew install imagemagick'));
                } else {
                    console.log('ImageMagick版本:', stdout.split('\n')[0]);
                    resolve();
                }
            });
        });
    } catch (imageMagickError) {
        throw imageMagickError;
    }

    try {
        // 步骤1: 使用LibreOffice将PPT转换为PDF
        console.log('步骤1: 将PPT转换为PDF...');
        const pdfFileName = path.basename(pptPath, path.extname(pptPath)) + '.pdf';
        const pdfPath = path.join(outputDir, pdfFileName);

        await new Promise((resolve, reject) => {
            const command = `libreoffice --headless --convert-to pdf --outdir "${outputDir}" "${pptPath}"`;
            console.log('执行命令:', command);

            exec(command, (error, stdout, stderr) => {
                if (error) {
                    console.error('LibreOffice转换失败:', error.message);
                    console.error('stderr:', stderr);
                    reject(error);
                } else {
                    console.log('LibreOffice转换成功:', stdout);
                    resolve();
                }
            });
        });

        // 检查PDF是否生成成功
        if (!fs.existsSync(pdfPath)) {
            throw new Error(`PDF文件未生成: ${pdfPath}`);
        }
        console.log('PDF生成成功:', pdfPath);

        // 步骤2: 使用ImageMagick将PDF转换为多页PNG图片
        console.log('步骤2: 使用ImageMagick将PDF转换为PNG图片...');

        await new Promise((resolve, reject) => {
            const pagePattern = path.join(outputDir, 'slide-%d.png');
            const command = `magick "${pdfPath}" "${pagePattern}"`;
            console.log('执行命令:', command);

            exec(command, (error, stdout, stderr) => {
                if (error) {
                    console.error('PDF转PNG失败:', error.message);
                    console.error('stderr:', stderr);
                    reject(error);
                } else {
                    console.log('PDF转PNG成功:', stdout);
                    if (stderr) console.log('stderr:', stderr); // ImageMagick 有时会在stderr输出警告
                    resolve();
                }
            });
        });

        // 等待文件生成完成
        console.log('等待文件生成...');
        await new Promise(resolve => setTimeout(resolve, 3000));

        // 检查输出目录中的所有文件
        console.log('输出目录中的所有文件:');
        const allFiles = fs.readdirSync(outputDir);
        console.log(allFiles);

        // 获取生成的PNG文件列表
        const pngFiles = allFiles
            .filter(file => file.startsWith('slide-') && file.endsWith('.png'))
            .sort((a, b) => {
                // 提取文件名中的数字进行排序
                const numA = parseInt(a.match(/slide-(\d+)\.png$/)?.[1] || '0');
                const numB = parseInt(b.match(/slide-(\d+)\.png$/)?.[1] || '0');
                return numA - numB;
            })
            .map(file => path.join(outputDir, file));

        console.log('生成的PNG文件:', pngFiles);

        if (pngFiles.length === 0) {
            throw new Error(`未生成任何PNG文件。输出目录: ${outputDir}, 所有文件: ${JSON.stringify(allFiles)}`);
        }

        // 清理PDF文件
        try {
            if (fs.existsSync(pdfPath)) {
                fs.unlinkSync(pdfPath);
                console.log('已清理临时PDF文件');
            }
        } catch (cleanupError) {
            console.warn('清理PDF文件失败:', cleanupError.message);
        }

        return pngFiles;

    } catch (conversionError) {
        console.error('转换过程中出错:', conversionError);
        throw conversionError;
    }
}

/**
 * 合并多张图片为长图
 * @param {Array} imagePaths 图片路径数组
 * @param {string} tempDir 临时目录
 * @param {string} fileId 文件ID
 * @returns {string} 长图路径
 */
async function combineImagesToLongImage(imagePaths, tempDir, fileId) {
    const { Jimp } = require('jimp');

    try {
        console.log("开始合并图片，共", imagePaths.length, "张");

        // 读取所有图片获取尺寸
        const images = [];
        let totalHeight = 0;
        let maxWidth = 0;

        for (let imagePath of imagePaths) {
            const img = await Jimp.read(imagePath);
            images.push(img);
            totalHeight += img.bitmap.height;
            maxWidth = Math.max(maxWidth, img.bitmap.width);
        }

        // 创建长图画布
        const longImage = new Jimp({ width: maxWidth, height: totalHeight, color: 0xFFFFFFFF });

        // 依次粘贴图片
        let currentY = 0;
        for (let img of images) {
            const x = Math.floor((maxWidth - img.bitmap.width) / 2); // 居中对齐
            longImage.composite(img, x, currentY);
            currentY += img.bitmap.height;
        }

        // 保存长图
        const longImagePath = path.join(tempDir, `long_image_${fileId}.png`);
        await longImage.write(longImagePath);

        console.log("长图合并完成:", longImagePath);
        return longImagePath;

    } catch (error) {
        console.error("图片合并失败:", error);
        throw error;
    }
}

/**
 * 压缩长图
 * @param {string} filePath 原始文件路径
 * @returns {Promise<string>} 返回压缩后的文件路径
 */
async function compressLongImage(filePath) {
    const { Jimp } = require('jimp');

    try {
        const originalSize = fs.statSync(filePath).size;
        console.log(`原始长图大小: ${(originalSize / 1024 / 1024).toFixed(2)} MB`);

        // 读取图片
        const image = await Jimp.read(filePath);
        const originalWidth = image.bitmap.width;
        const originalHeight = image.bitmap.height;

        console.log(`原始长图尺寸: ${originalWidth}x${originalHeight}`);

        // 更激进的压缩：宽度限制在600px
        if (originalWidth > 600) {
            const scale = 600 / originalWidth;
            const newWidth = 600;
            const newHeight = Math.floor(originalHeight * scale);
            console.log(`缩小长图尺寸到: ${newWidth}x${newHeight}`);
            image.resize({ w: newWidth, h: newHeight });
        }

        // 生成压缩后的临时文件路径（转换为JPEG以获得更好的压缩效果）
        const compressedPath = filePath.replace('.png', '_compressed.jpg');

        // 转换为JPEG格式并保存，降低质量到50以获得更小的文件
        await image.write(compressedPath, { quality: 50 });

        const compressedSize = fs.statSync(compressedPath).size;
        const compressionRatio = ((1 - compressedSize / originalSize) * 100).toFixed(2);
        console.log(`压缩后长图大小: ${(compressedSize / 1024 / 1024).toFixed(2)} MB (压缩率: ${compressionRatio}%)`);

        return compressedPath;
    } catch (error) {
        console.error("长图压缩失败:", error);
        throw error;
    }
}

/**
 * 压缩缩略图
 * @param {string} filePath 原始文件路径
 * @returns {Promise<string>} 返回压缩后的文件路径
 */
async function compressThumbnail(filePath) {
    const { Jimp } = require('jimp');

    try {
        const originalSize = fs.statSync(filePath).size;
        console.log(`原始缩略图大小: ${(originalSize / 1024 / 1024).toFixed(2)} MB`);

        // 读取图片
        const image = await Jimp.read(filePath);
        const originalWidth = image.bitmap.width;
        const originalHeight = image.bitmap.height;

        console.log(`原始缩略图尺寸: ${originalWidth}x${originalHeight}`);

        // 缩略图压缩到宽度800px（比长图更小）
        if (originalWidth > 800) {
            const scale = 800 / originalWidth;
            const newWidth = 800;
            const newHeight = Math.floor(originalHeight * scale);
            console.log(`缩小缩略图尺寸到: ${newWidth}x${newHeight}`);
            image.resize({ w: newWidth, h: newHeight });
        }

        // 生成压缩后的临时文件路径（转换为JPEG）
        const compressedPath = filePath.replace(/\.(png|jpg|jpeg)$/i, '_thumb_compressed.jpg');

        // 转换为JPEG格式并保存，质量60在清晰度和文件大小之间取得平衡
        await image.write(compressedPath, { quality: 60 });

        const compressedSize = fs.statSync(compressedPath).size;
        const compressionRatio = ((1 - compressedSize / originalSize) * 100).toFixed(2);
        console.log(`压缩后缩略图大小: ${(compressedSize / 1024 / 1024).toFixed(2)} MB (压缩率: ${compressionRatio}%)`);

        return compressedPath;
    } catch (error) {
        console.error("缩略图压缩失败:", error);
        throw error;
    }
}

/**
 * 等待文件写入完成（检查文件大小稳定）
 * @param {string} filePath 文件路径
 * @param {number} maxWaitTime 最大等待时间（毫秒）
 */
async function waitForFileStable(filePath, maxWaitTime = 10000, minSize = 1024) {
    let lastSize = 0;
    let stableCount = 0;
    const checkInterval = 500; // 每500ms检查一次
    const requiredStableChecks = 4; // 需要连续4次大小不变
    const startTime = Date.now();

    while (Date.now() - startTime < maxWaitTime) {
        try {
            const stats = fs.statSync(filePath);
            const currentSize = stats.size;

            console.log(`📊 文件大小检查: ${(currentSize / 1024 / 1024).toFixed(2)} MB`);

            // 确保文件大小至少达到最小值
            if (currentSize < minSize) {
                console.log(`⏳ 文件太小 (${currentSize} bytes)，继续等待...`);
                await new Promise(resolve => setTimeout(resolve, checkInterval));
                continue;
            }

            if (currentSize === lastSize && currentSize > 0) {
                stableCount++;
                console.log(`🔄 文件大小稳定 (${stableCount}/${requiredStableChecks})`);

                if (stableCount >= requiredStableChecks) {
                    // 额外验证：尝试读取文件头
                    try {
                        const fd = fs.openSync(filePath, 'r');
                        const headerBuffer = Buffer.alloc(4);
                        fs.readSync(fd, headerBuffer, 0, 4, 0);
                        fs.closeSync(fd);

                        console.log('✅ 文件写入完成，大小稳定且可读');
                        return true;
                    } catch (readError) {
                        console.warn('⚠️ 文件头读取失败，继续等待:', readError.message);
                        stableCount = 0;
                    }
                }
            } else {
                if (stableCount > 0) {
                    console.log(`🔄 文件大小变化: ${lastSize} → ${currentSize} bytes`);
                }
                stableCount = 0;
            }

            lastSize = currentSize;
            await new Promise(resolve => setTimeout(resolve, checkInterval));
        } catch (error) {
            console.warn(`⚠️ 文件检查出错: ${error.message}`);
            await new Promise(resolve => setTimeout(resolve, checkInterval));
        }
    }

    console.log('⚠️ 文件稳定性检查超时，继续尝试处理');
    return false;
}

/**
 * 从老式PPT文件（OLE2格式）中提取嵌入的图片
 * @param {string} pptPath PPT文件路径
 * @param {string} tempDir 临时目录
 * @param {string} fileId 文件ID
 * @returns {Promise<Array>} 返回提取的图片信息数组
 */
async function extractImagesFromOldPPT(pptPath, tempDir, fileId) {
    const CFB = require('cfb');
    const { Jimp } = require('jimp');

    try {
        console.log('📖 读取老式PPT文件（OLE2格式）...');

        // 读取CFB文件
        const fileData = fs.readFileSync(pptPath);
        const cfb = CFB.read(fileData, { type: 'buffer' });

        console.log('✅ OLE2文件解析成功');

        // 创建图片输出目录
        const imagesDir = path.join(tempDir, 'old_ppt_images');
        if (!fs.existsSync(imagesDir)) {
            fs.mkdirSync(imagesDir, { recursive: true });
        }

        // 查找Pictures流（老式PPT将所有图片打包在此流中）
        const picturesEntry = cfb.FileIndex.find(entry => entry.name === 'Pictures');

        if (!picturesEntry || !picturesEntry.content) {
            console.log('⚠️ 未找到Pictures流');
            return [];
        }

        console.log(`✅ 找到Pictures流，大小: ${(picturesEntry.content.length / 1024).toFixed(2)} KB`);
        console.log('🔍 从Pictures流中扫描所有图片...');

        const content = picturesEntry.content;
        let offset = 0;

        // 🎯 优化：先扫描所有图片，然后只保存后20张
        const allImageData = [];

        // 扫描Pictures流，寻找图片的魔数标记
        while (offset < content.length - 4) {
            const byte0 = content[offset];
            const byte1 = content[offset + 1];
            const byte2 = content[offset + 2];
            const byte3 = content[offset + 3];

            let imageType = null;
            let imageStart = offset;

            // 检测图片格式
            if (byte0 === 0xFF && byte1 === 0xD8) {
                imageType = 'JPEG';
            } else if (byte0 === 0x89 && byte1 === 0x50 && byte2 === 0x4E && byte3 === 0x47) {
                imageType = 'PNG';
            } else if (byte0 === 0x47 && byte1 === 0x49 && byte2 === 0x46) {
                imageType = 'GIF';
            } else if (byte0 === 0x42 && byte1 === 0x4D) {
                imageType = 'BMP';
            }

            if (imageType) {
                // 找到图片的结尾位置
                let imageEnd = offset + 1;

                if (imageType === 'JPEG') {
                    // JPEG结束标记: FF D9
                    for (let i = offset + 2; i < content.length - 1; i++) {
                        if (content[i] === 0xFF && content[i + 1] === 0xD9) {
                            imageEnd = i + 2;
                            break;
                        }
                    }
                } else {
                    // 对于其他格式，找到下一个图片的开始位置
                    for (let i = offset + 10; i < content.length - 4; i++) {
                        const nb0 = content[i];
                        const nb1 = content[i + 1];
                        if ((nb0 === 0xFF && nb1 === 0xD8) ||
                            (nb0 === 0x89 && nb1 === 0x50) ||
                            (nb0 === 0x47 && nb1 === 0x49) ||
                            (nb0 === 0x42 && nb1 === 0x4D)) {
                            imageEnd = i;
                            break;
                        }
                    }
                    if (imageEnd === offset + 1) {
                        imageEnd = content.length;
                    }
                }

                const imageData = content.slice(imageStart, imageEnd);

                // 只记录大于1KB的图片（过滤掉图标等小图片）
                if (imageData.length > 1024) {
                    const ext = imageType === 'JPEG' ? '.jpg' : imageType === 'PNG' ? '.png' :
                               imageType === 'GIF' ? '.gif' : '.bmp';
                    allImageData.push({ imageData, ext, imageType });
                }

                offset = imageEnd;
            } else {
                offset++;
            }
        }

        console.log(`📁 扫描到${allImageData.length}张图片`);

        // 按文件大小过滤，只保存大于100KB的图片（过滤掉小图标、装饰图等）
        const minFileSize = 100 * 1024; // 100KB
        const imagesToSave = allImageData.filter(img => img.imageData.length >= minFileSize);

        console.log(`🎯 按文件大小过滤：从${allImageData.length}张图片中筛选出${imagesToSave.length}张大图（≥100KB）`);

        // 保存选中的图片
        for (let i = 0; i < imagesToSave.length; i++) {
            const { imageData, ext, imageType } = imagesToSave[i];
            // 找到当前图片在原始列表中的索引
            const actualIndex = allImageData.indexOf(imagesToSave[i]) + 1;
            const imagePath = path.join(imagesDir, `image_${actualIndex}${ext}`);

            fs.writeFileSync(imagePath, imageData);
            console.log(`📷 提取图片 #${actualIndex}: ${imageType}, ${(imageData.length / 1024).toFixed(2)} KB`);
        }

        console.log(`\n✅ 从老式PPT中提取了${imagesToSave.length}张图片`);

        // 现在分析提取的图片
        if (imagesToSave.length === 0) {
            console.log('⚠️ 未找到任何图片');
            return [];
        }

        // 读取并分析图片
        const imageFiles = fs.readdirSync(imagesDir);

        // 按文件名中的数字排序
        imageFiles.sort((a, b) => {
            const numA = parseInt(a.match(/(\d+)/)?.[1] || '0', 10);
            const numB = parseInt(b.match(/(\d+)/)?.[1] || '0', 10);
            return numA - numB;
        });

        const analyzedImages = [];
        const totalImages = allImageData.length; // 总图片数

        for (let i = 0; i < imageFiles.length; i++) {
            const imagePath = path.join(imagesDir, imageFiles[i]);
            const stats = fs.statSync(imagePath);

            try {
                // 只分析真正的图片格式
                if (!/\.(jpg|jpeg|png|gif|bmp)$/i.test(imageFiles[i])) {
                    continue;
                }

                const image = await Jimp.read(imagePath);
                const width = image.bitmap.width;
                const height = image.bitmap.height;
                const fileSize = stats.size;

                // 从文件名中提取实际索引（image_41.jpg -> 41）
                const actualIndex = parseInt(imageFiles[i].match(/(\d+)/)?.[1] || '0', 10) - 1;

                console.log(`\n分析图片${i + 1}/${imageFiles.length}: ${imageFiles[i]} (总序号: ${actualIndex + 1}/${totalImages})`);
                console.log(`  尺寸: ${width}x${height}, 文件大小: ${(fileSize / 1024).toFixed(2)} KB`);

                // 🚨 过滤不合适的图片（横幅、小图标等）
                const aspectRatio = width / height;
                const minDimension = Math.min(width, height);

                // 检查1: 宽高比异常（横幅或竖条）
                if (aspectRatio < 0.4 || aspectRatio > 2.5) {
                    console.log(`  ⚠️ 跳过：宽高比异常 (${aspectRatio.toFixed(2)}:1)，可能是横幅或装饰图`);
                    continue;
                }

                // 检查2: 文件太小（小于50KB）
                if (fileSize < 50 * 1024) {
                    console.log(`  ⚠️ 跳过：文件太小 (${(fileSize / 1024).toFixed(2)} KB)，可能是图标或按钮`);
                    continue;
                }

                // 检查3: 尺寸太小（任一边小于200px）
                if (minDimension < 200) {
                    console.log(`  ⚠️ 跳过：尺寸太小 (${width}x${height}px)，可能是小图标`);
                    continue;
                }

                // 使用实际索引和总图片数计算评分
                const score = calculateImageScore(image, fileSize, actualIndex, totalImages);

                analyzedImages.push({
                    path: imagePath,
                    filename: imageFiles[i],
                    width,
                    height,
                    fileSize,
                    score,
                    index: actualIndex,
                    totalImages: totalImages
                });

                console.log(`  ✅ 评分: ${score.toFixed(2)}`);

            } catch (imageError) {
                console.warn(`⚠️ 分析图片失败: ${imageFiles[i]}`, imageError.message);
            }
        }

        // 按评分排序，评分相同时优先选择编号较大的图片
        analyzedImages.sort((a, b) => {
            // 首先按评分降序
            if (b.score !== a.score) {
                return b.score - a.score;
            }

            // 评分相同时，提取文件名中的数字并比较（编号大的优先）
            const extractNumber = (filename) => {
                const match = filename.match(/(\d+)/);
                return match ? parseInt(match[1], 10) : 0;
            };

            const numA = extractNumber(a.filename);
            const numB = extractNumber(b.filename);

            // 编号大的排在前面
            return numB - numA;
        });

        console.log(`\n✅ 成功分析${analyzedImages.length}张图片`);
        if (analyzedImages.length > 0) {
            console.log(`🏆 评分最高: ${analyzedImages[0].filename} (评分: ${analyzedImages[0].score.toFixed(2)})`);
        }

        return analyzedImages;

    } catch (error) {
        console.error('❌ 从老式PPT提取图片失败:', error);
        throw error;
    }
}

/**
 * 从PPT文件中提取所有嵌入的图片
 * @param {string} pptPath PPT文件路径
 * @param {string} tempDir 临时目录
 * @param {string} fileId 文件ID
 * @returns {Promise<Array>} 返回提取的图片信息数组
 */
async function extractImagesFromPPT(pptPath, tempDir, fileId) {
    const StreamZip = require('node-stream-zip');
    const { Jimp } = require('jimp');

    try {
        console.log('🔓 开始解压PPT文件...');

        // 检查PPT文件是否存在
        if (!fs.existsSync(pptPath)) {
            throw new Error(`PPT文件不存在: ${pptPath}`);
        }

        // 等待文件完全写入磁盘（增强检查）
        console.log('⏳ 等待文件写入完成...');
        const isStable = await waitForFileStable(pptPath, 15000, 10240); // 等待最多15秒，最小10KB

        if (!isStable) {
            console.warn('⚠️ 文件可能未完全写入，但继续尝试处理');
        }

        // 验证PPT文件完整性
        const fileSize = fs.statSync(pptPath).size;
        console.log(`📊 文件最终大小: ${(fileSize / 1024 / 1024).toFixed(2)} MB`);

        if (fileSize < 10240) {
            throw new Error(`PPT文件太小 (${fileSize} bytes)，可能损坏或未完全下载`);
        }

        // 🔍 检查文件格式：PPTX是ZIP格式，老式PPT是二进制格式
        // 只读取文件前4个字节，避免读取整个大文件
        const fd = fs.openSync(pptPath, 'r');
        const firstBytes = Buffer.alloc(4);
        fs.readSync(fd, firstBytes, 0, 4, 0);
        fs.closeSync(fd);

        // ZIP文件的魔数是 "PK\x03\x04" (0x50 0x4B 0x03 0x04)
        const isZipFormat = firstBytes[0] === 0x50 && firstBytes[1] === 0x4B &&
                           firstBytes[2] === 0x03 && firstBytes[3] === 0x04;

        // 老式PPT文件的魔数是 "Ðà¼¾" (0xD0 0xCF 0x11 0xE0)
        const isOldPptFormat = firstBytes[0] === 0xD0 && firstBytes[1] === 0xCF;

        if (isOldPptFormat) {
            console.log('⚠️ 检测到老式.ppt格式（二进制OLE2格式）');
            console.log('🔄 尝试使用OLE2解析器提取嵌入图片...');
            // 使用专门的函数处理老式PPT文件
            return await extractImagesFromOldPPT(pptPath, tempDir, fileId);
        }

        if (!isZipFormat) {
            console.log('⚠️ 文件不是有效的ZIP格式');
            throw new Error('文件格式不正确，不是有效的PPTX文件');
        }

        console.log('✅ 文件格式检测通过：ZIP格式的PPTX文件');

        // 使用 node-stream-zip 打开文件（支持大型ZIP文件）
        let zip;
        try {
            // 添加额外的延迟，确保文件句柄已释放
            await new Promise(resolve => setTimeout(resolve, 1000));

            zip = new StreamZip.async({
                file: pptPath,
                storeEntries: true, // 存储条目信息
                skipEntryNameValidation: true // 跳过条目名称验证，兼容性更好
            });

            console.log('✅ PPT文件读取成功（使用流式ZIP处理）');
        } catch (zipError) {
            console.error('❌ ZIP文件打开失败详情:', zipError);
            throw new Error(`PPT文件打开失败: ${zipError.message}`);
        }

        try {
            // 🎯 新策略：按页面顺序提取图片，避免复用图片影响位置评分
            // 1. 获取所有slide文件
            const entries = await zip.entries();
            const xml2js = require('xml2js');
            const parser = new xml2js.Parser();

            // 获取所有slide并按编号排序
            const slideFiles = Object.keys(entries)
                .filter(name => name.match(/^ppt\/slides\/slide\d+\.xml$/))
                .sort((a, b) => {
                    const numA = parseInt(a.match(/slide(\d+)\.xml$/)[1], 10);
                    const numB = parseInt(b.match(/slide(\d+)\.xml$/)[1], 10);
                    return numA - numB;
                });

            const totalSlides = slideFiles.length;
            console.log(`📊 PPT共有 ${totalSlides} 页`);

            // 2. 遍历每个slide，按页面顺序收集图片
            const imagesBySlide = []; // 存储: {slideNum, imagePath, imageSize}
            const extractedImagePaths = new Set(); // 去重：避免重复提取同一张图片

            for (let i = 0; i < slideFiles.length; i++) {
                const slideFile = slideFiles[i];
                const slideNum = parseInt(slideFile.match(/slide(\d+)\.xml$/)[1], 10);
                const relsPath = slideFile.replace('slides/', 'slides/_rels/') + '.rels';

                if (!entries[relsPath]) continue;

                try {
                    // 读取关系文件
                    const relsData = await zip.entryData(relsPath);
                    const relsXml = relsData.toString('utf8');
                    const relsResult = await parser.parseStringPromise(relsXml);

                    if (relsResult.Relationships && relsResult.Relationships.Relationship) {
                        for (const rel of relsResult.Relationships.Relationship) {
                            const target = rel.$.Target;

                            // 检查是否是图片
                            if (target.includes('../media/') && /\.(jpg|jpeg|png|gif|bmp)$/i.test(target)) {
                                // 转换相对路径为绝对路径
                                const imagePath = target.replace('../media/', 'ppt/media/');

                                // 检查图片大小
                                if (entries[imagePath] && entries[imagePath].size >= 100 * 1024) {
                                    imagesBySlide.push({
                                        slideNum: slideNum,
                                        imagePath: imagePath,
                                        imageSize: entries[imagePath].size,
                                        imageEntry: entries[imagePath]
                                    });
                                }
                            }
                        }
                    }
                } catch (err) {
                    console.warn(`⚠️ 解析slide${slideNum}的关系文件失败:`, err.message);
                }
            }

            console.log(`📁 按页面顺序找到 ${imagesBySlide.length} 张大图（≥100KB）`);

            if (imagesBySlide.length === 0) {
                console.log('⚠️ 未找到符合条件的图片');
                await zip.close();
                return [];
            }

            // 3. 去重处理：如果同一张图片在多个页面出现，只保留最后一次（页码最大的）
            const imageMap = new Map(); // key: imagePath, value: {slideNum, ...}
            for (const img of imagesBySlide) {
                if (!imageMap.has(img.imagePath) || imageMap.get(img.imagePath).slideNum < img.slideNum) {
                    imageMap.set(img.imagePath, img);
                }
            }

            const uniqueImages = Array.from(imageMap.values());
            console.log(`🔄 去重后剩余 ${uniqueImages.length} 张图片（同一图片保留最后出现的页面）`);

            // 创建临时目录用于存放提取的图片
            const extractDir = path.join(tempDir, 'extracted_images');
            if (!fs.existsSync(extractDir)) {
                fs.mkdirSync(extractDir, { recursive: true });
            }

            // 4. 提取并分析这些图片
            const images = [];
            for (let i = 0; i < uniqueImages.length; i++) {
                const imgInfo = uniqueImages[i];
                const filename = path.basename(imgInfo.imagePath);
                const localImagePath = path.join(extractDir, `slide${imgInfo.slideNum}_${filename}`);

                try {
                    // 提取图片到临时目录
                    const imageData = await zip.entryData(imgInfo.imagePath);
                    fs.writeFileSync(localImagePath, imageData);

                    // 读取图片分析其属性
                    const image = await Jimp.read(localImagePath);
                    const width = image.bitmap.width;
                    const height = image.bitmap.height;
                    const imgFileSize = imageData.length;

                    console.log(`\n分析图片${i + 1}/${uniqueImages.length}: ${filename} (第${imgInfo.slideNum}页)`);
                    console.log(`  尺寸: ${width}x${height}, 文件大小: ${(imgFileSize / 1024).toFixed(2)} KB`);

                    // 🚨 过滤不合适的图片（横幅、小图标等）
                    const aspectRatio = width / height;
                    const minDimension = Math.min(width, height);

                    // 检查1: 宽高比异常（横幅或竖条）
                    if (aspectRatio < 0.4 || aspectRatio > 2.5) {
                        console.log(`  ⚠️ 跳过：宽高比异常 (${aspectRatio.toFixed(2)}:1)，可能是横幅或装饰图`);
                        continue;
                    }

                    // 检查2: 文件太小（小于50KB）
                    if (imgFileSize < 50 * 1024) {
                        console.log(`  ⚠️ 跳过：文件太小 (${(imgFileSize / 1024).toFixed(2)} KB)，可能是图标或按钮`);
                        continue;
                    }

                    // 检查3: 尺寸太小（任一边小于200px）
                    if (minDimension < 200) {
                        console.log(`  ⚠️ 跳过：尺寸太小 (${width}x${height}px)，可能是小图标`);
                        continue;
                    }

                    // ✅ 关键改进：使用页面位置而不是图片编号来计算评分
                    // slideNum - 1 是因为索引从0开始
                    const slideIndex = imgInfo.slideNum - 1;
                    const score = calculateImageScore(image, imgFileSize, slideIndex, totalSlides);

                    images.push({
                        path: localImagePath,
                        filename: filename,
                        slideNum: imgInfo.slideNum,
                        width,
                        height,
                        fileSize: imgFileSize,
                        score,
                        index: slideIndex,
                        totalImages: totalSlides
                    });

                    console.log(`  ✅ 评分: ${score.toFixed(2)} (页面位置: ${imgInfo.slideNum}/${totalSlides})`);

                } catch (imageError) {
                    console.warn(`⚠️ 分析图片失败: ${filename}`, imageError.message);
                }
            }

            // 关闭 ZIP 文件
            await zip.close();

            // 按评分排序，评分相同时优先选择页面位置靠后的图片
            images.sort((a, b) => {
                // 首先按评分降序
                if (b.score !== a.score) {
                    return b.score - a.score;
                }

                // 评分相同时，优先选择页面位置靠后的图片（作品图通常在后面）
                return b.slideNum - a.slideNum;
            });

            console.log(`\n✅ 成功分析${images.length}张图片`);
            if (images.length > 0) {
                console.log(`🏆 评分最高: ${images[0].filename} 来自第${images[0].slideNum}页 (评分: ${images[0].score.toFixed(2)})`);
            }

            return images;

        } catch (error) {
            // 确保关闭 ZIP 文件
            if (zip) {
                await zip.close();
            }
            throw error;
        }

    } catch (error) {
        console.error('❌ 提取PPT图片失败:', error);
        throw error;
    }
}

/**
 * 计算图片质量评分（用于筛选作品图）
 * @param {Jimp} image Jimp图片对象
 * @param {number} fileSize 文件大小（字节）
 * @param {number} imageIndex 图片在列表中的索引（从0开始）
 * @param {number} totalImages 图片总数
 * @returns {number} 评分
 */
function calculateImageScore(image, fileSize, imageIndex = 0, totalImages = 1) {
    const width = image.bitmap.width;
    const height = image.bitmap.height;

    let score = 0;

    // 计算位置百分比（全局使用）
    const position = totalImages > 1 ? imageIndex / Math.max(totalImages - 1, 1) : 0;

    // 位置信息对象（用于日志输出）
    let positionInfo = {
        position: position,
        positionPercent: (position * 100).toFixed(1),
        baseScore: 0,
        continuousScore: 0,
        superBonus: 0,
        totalPositionScore: 0
    };

    // 1. 图片位置评分（作品图通常在PPT后面）
    // ⚠️ 这是最重要的特征，给予最高权重！
    // 使用连续评分函数，让位置越靠后得分越高，差异更明显
    let positionScore = 0;
    if (totalImages > 1) {

        // 策略1: 基础位置分（阶梯式，快速区分大范围）
        let basePositionScore = 0;
        if (position > 0.9) {
            basePositionScore = 12000; // 后10%，大幅提高
        } else if (position > 0.8) {
            basePositionScore = 9000;  // 80-90%
        } else if (position > 0.7) {
            basePositionScore = 6000;  // 70-80%
        } else if (position > 0.5) {
            basePositionScore = 3000;  // 50-70%
        } else if (position > 0.25) {
            basePositionScore = 1000;  // 25-50%
        }

        // 策略2: 连续位置加分（让位置差异更细腻）
        // 使用二次函数，让越靠后的图片加成越大
        // position^2 * 8000，范围0-8000分
        const continuousPositionScore = Math.pow(position, 2) * 8000;

        // 策略3: 后10%超级位置加成（让位置>90%的图片之间拉开差距）
        // 这是作品图的高概率区域，位置越靠后优势越大
        let superPositionBonus = 0;
        if (position > 0.90) {
            // 在90%-100%范围内，每增加1%，额外+500分
            // 例如：92.9%比91.1%多1.8%，额外多900分
            const extraPercent = (position - 0.90) * 100; // 0-10
            superPositionBonus = extraPercent * 500; // 最多5000分
        }

        positionScore = basePositionScore + continuousPositionScore + superPositionBonus;
        score += positionScore;

        // 更新位置信息供日志使用
        positionInfo.baseScore = basePositionScore;
        positionInfo.continuousScore = Math.round(continuousPositionScore);
        positionInfo.superBonus = Math.round(superPositionBonus);
        positionInfo.totalPositionScore = Math.round(positionScore);
    }

    // 2. 文件大小评分（增加权重，文件大说明细节丰富）
    // 🎯 优化：文件大小是判断作品图的重要特征，提高权重
    if (fileSize > 1024 * 1024) {
        score += 3000; // 大于1MB
    } else if (fileSize > 500 * 1024) {
        score += 2500; // 500KB-1MB
    } else if (fileSize > 250 * 1024) {
        score += 2000; // 250KB-500KB
    } else if (fileSize > 150 * 1024) {
        score += 1500; // 150KB-250KB
    } else if (fileSize > 100 * 1024) {
        score += 1000; // 100KB-150KB
    } else {
        score += 300; // 小图片可能是图标或装饰
    }

    // 3. 图片尺寸评分（适中权重，作品图通常尺寸较大）
    const pixels = width * height;
    if (pixels > 1000000) { // 大于1百万像素
        score += 1500;
    } else if (pixels > 500000) {
        score += 1200;
    } else if (pixels > 200000) {
        score += 800;
    } else {
        score += 200;
    }

    // 4. 宽高比评分（正常比例的图片更可能是作品）
    const aspectRatio = width / height;
    if (aspectRatio >= 0.5 && aspectRatio <= 2.0) {
        score += 1000; // 正常比例
    } else {
        score += 300; // 过宽或过高的图片可能是装饰
    }

    // 4. 色彩分析评分（作品图的关键特征）
    try {
        const colorSet = new Set();
        const hueMap = new Map();
        let totalBrightness = 0;
        let totalSaturation = 0;
        let pixelCount = 0;
        let edgePixels = 0;
        const edgeThreshold = 30;

        // 采样分析（每10个像素采样一次，提高精度）
        for (let y = 0; y < height; y += 10) {
            for (let x = 0; x < width; x += 10) {
                const color = image.getPixelColor(x, y);
                colorSet.add(color);

                const r = (color >> 24) & 0xFF;
                const g = (color >> 16) & 0xFF;
                const b = (color >> 8) & 0xFF;

                // 计算亮度
                const brightness = (r + g + b) / 3;
                totalBrightness += brightness;

                // 计算饱和度
                const max = Math.max(r, g, b);
                const min = Math.min(r, g, b);
                const saturation = max === 0 ? 0 : (max - min) / max;
                totalSaturation += saturation;

                // 计算色相
                let hue = 0;
                if (max !== min) {
                    const delta = max - min;
                    if (max === r) {
                        hue = ((g - b) / delta + (g < b ? 6 : 0)) / 6;
                    } else if (max === g) {
                        hue = ((b - r) / delta + 2) / 6;
                    } else {
                        hue = ((r - g) / delta + 4) / 6;
                    }
                }
                const hueBucket = Math.floor(hue * 12);
                hueMap.set(hueBucket, (hueMap.get(hueBucket) || 0) + 1);

                // 检测边缘（简单的差分）
                if (x > 0 && y > 0) {
                    const prevColor = image.getPixelColor(x - 10, y);
                    const prevR = (prevColor >> 24) & 0xFF;
                    const prevG = (prevColor >> 16) & 0xFF;
                    const prevB = (prevColor >> 8) & 0xFF;
                    const diff = Math.abs(r - prevR) + Math.abs(g - prevG) + Math.abs(b - prevB);
                    if (diff > edgeThreshold) {
                        edgePixels++;
                    }
                }

                pixelCount++;
            }
        }

        const colorDiversity = colorSet.size;
        const avgBrightness = totalBrightness / pixelCount;
        const avgSaturation = totalSaturation / pixelCount;
        const hueVariety = hueMap.size;
        const edgeDensity = edgePixels / pixelCount;

        // 4.1 颜色多样性评分（提高细粒度，让更丰富的颜色有明显优势）
        if (colorDiversity > 20000) {
            score += 5500;
        } else if (colorDiversity > 10000) {
            score += 4500;
        } else if (colorDiversity > 5000) {
            score += 3500;
        } else if (colorDiversity > 4000) {
            // 新增4000+档位，让4331种颜色比4155种有优势
            score += 3200;
        } else if (colorDiversity > 3000) {
            score += 2800;
        } else if (colorDiversity > 2000) {
            score += 2000;
        } else if (colorDiversity > 500) {
            score += 1000;
        } else {
            score += 300;
        }

        // 4.2 色相种类评分（增加12种色相的奖励，拉开与11种的差距）
        if (hueVariety >= 12) {
            score += 4500; // 满色谱，额外奖励500分
        } else if (hueVariety >= 11) {
            score += 4000; // 接近全色谱
        } else if (hueVariety >= 9) {
            score += 3000;
        } else if (hueVariety >= 7) {
            score += 2000;
        } else if (hueVariety >= 5) {
            score += 1000;
        } else {
            score += 300;
        }

        // 4.3 边缘密度评分（智能评分，带位置保护）
        // ⚠️ 关键优化：位置靠后的图片（>90%）边缘密度高是作品图的正常特征，不惩罚！
        let edgeScore = 0;

        // 位置保护机制：后10%的图片即使边缘密度高也不惩罚
        const isInPosterZone = position > 0.90; // 后10%是作品图高概率区域

        if (isInPosterZone) {
            // 后10%位置的图片：边缘密度高 = 作品图细节丰富的特征，给予奖励！
            if (edgeDensity > 0.40) {
                edgeScore = 4000; // 边缘密度高，说明细节丰富，奖励
            } else if (edgeDensity > 0.30) {
                edgeScore = 3500;
            } else if (edgeDensity > 0.20) {
                edgeScore = 3200;
            } else if (edgeDensity > 0.15) {
                edgeScore = 2800;
            } else if (edgeDensity > 0.08) {
                edgeScore = 1500;
            } else {
                edgeScore = 500;
            }
        } else {
            // 前90%位置的图片：边缘密度过高可能是纹理图，需要谨慎
            if (edgeDensity > 0.60) {
                // 边缘密度超过60%，极可能是纹理图或图标
                edgeScore = 500 - 2000; // 净-1500分
            } else if (edgeDensity > 0.50) {
                // 边缘密度50%-60%，很可能是纹理图
                edgeScore = 1000 - 1000; // 净0分，不奖励不惩罚
            } else if (edgeDensity > 0.38) {
                // 边缘密度38%-50%，可能是纹理图，轻微惩罚
                edgeScore = 1500;
            } else if (edgeDensity > 0.25) {
                // 边缘密度25%-38%，适中范围
                edgeScore = 3000;
            } else if (edgeDensity > 0.20) {
                // 边缘密度20%-25%，好的范围
                edgeScore = 3200;
            } else if (edgeDensity > 0.15) {
                // 边缘密度15%-20%，不错
                edgeScore = 2800;
            } else if (edgeDensity > 0.08) {
                edgeScore = 1500;
            } else {
                edgeScore = 500;
            }
        }
        score += edgeScore;

        // 4.4 饱和度评分（作品图通常色彩鲜艳）
        if (avgSaturation > 0.3) {
            score += 1500;
        } else if (avgSaturation > 0.2) {
            score += 1000;
        } else {
            score += 300;
        }

        // 4.5 亮度合理性评分（避免纯白或纯黑背景）
        if (avgBrightness > 50 && avgBrightness < 200) {
            score += 1000;
        } else {
            score += 300;
        }

        // 4.6 图案重复性检测（新增，检测纹理图特征）
        // 将图片分成4x4的网格，比较相邻块的相似度
        let patternRepetitionScore = 0;
        try {
            const blockWidth = Math.floor(width / 4);
            const blockHeight = Math.floor(height / 4);
            let totalSimilarity = 0;
            let comparisonCount = 0;

            // 比较相邻块的颜色分布
            for (let by = 0; by < 3; by++) {
                for (let bx = 0; bx < 3; bx++) {
                    const x1 = bx * blockWidth;
                    const y1 = by * blockHeight;
                    const x2 = (bx + 1) * blockWidth;
                    const y2 = by * blockHeight;

                    // 采样两个相邻块的平均颜色
                    let r1 = 0, g1 = 0, b1 = 0, count1 = 0;
                    let r2 = 0, g2 = 0, b2 = 0, count2 = 0;

                    for (let sy = 0; sy < blockHeight && y1 + sy < height; sy += 20) {
                        for (let sx = 0; sx < blockWidth && x1 + sx < width; sx += 20) {
                            const color1 = image.getPixelColor(x1 + sx, y1 + sy);
                            r1 += (color1 >> 24) & 0xFF;
                            g1 += (color1 >> 16) & 0xFF;
                            b1 += (color1 >> 8) & 0xFF;
                            count1++;

                            if (x2 + sx < width) {
                                const color2 = image.getPixelColor(x2 + sx, y2 + sy);
                                r2 += (color2 >> 24) & 0xFF;
                                g2 += (color2 >> 16) & 0xFF;
                                b2 += (color2 >> 8) & 0xFF;
                                count2++;
                            }
                        }
                    }

                    if (count1 > 0 && count2 > 0) {
                        r1 /= count1; g1 /= count1; b1 /= count1;
                        r2 /= count2; g2 /= count2; b2 /= count2;

                        // 计算颜色差异
                        const colorDiff = Math.abs(r1 - r2) + Math.abs(g1 - g2) + Math.abs(b1 - b2);
                        const similarity = 1 - Math.min(colorDiff / 255, 1);
                        totalSimilarity += similarity;
                        comparisonCount++;
                    }
                }
            }

            const avgSimilarity = comparisonCount > 0 ? totalSimilarity / comparisonCount : 0;

            // 相似度高表示图案重复（纹理图特征），给予惩罚
            // 🎯 优化：位置>95%时，降低重复性检测权重，避免误判真实作品图
            if (avgSimilarity > 0.85) {
                patternRepetitionScore = -2000; // 高重复性，严重惩罚
            } else if (avgSimilarity > 0.75) {
                patternRepetitionScore = -1000; // 中等重复性
            } else if (avgSimilarity > 0.65) {
                patternRepetitionScore = -500; // 轻微重复性
            } else if (avgSimilarity < 0.40) {
                patternRepetitionScore = 1500; // 低重复性，奖励（真实作品图特征）
            } else if (avgSimilarity < 0.50) {
                patternRepetitionScore = 1000;
            } else {
                patternRepetitionScore = 500;
            }

            // 🎯 关键优化：位置>95%时，重复性检测权重降低70%（作品图高概率区域）
            if (position > 0.95) {
                patternRepetitionScore = Math.round(patternRepetitionScore * 0.3);
            }

            score += patternRepetitionScore;
        } catch (patternError) {
            console.warn('图案重复性检测失败:', patternError.message);
        }

        // 4.7 颜色分布均匀性检测（新增，检测背景图特征）
        // 背景图/纹理图的颜色分布通常很均匀，作品图通常有明显的主体和背景
        let colorDistributionScore = 0;
        try {
            // 将图片分成3x3网格，统计每个网格的平均颜色
            const gridSize = 3;
            const cellWidth = Math.floor(width / gridSize);
            const cellHeight = Math.floor(height / gridSize);
            const cellColors = [];

            for (let gy = 0; gy < gridSize; gy++) {
                for (let gx = 0; gx < gridSize; gx++) {
                    let r = 0, g = 0, b = 0, count = 0;
                    const startX = gx * cellWidth;
                    const startY = gy * cellHeight;

                    for (let sy = 0; sy < cellHeight && startY + sy < height; sy += 15) {
                        for (let sx = 0; sx < cellWidth && startX + sx < width; sx += 15) {
                            const color = image.getPixelColor(startX + sx, startY + sy);
                            r += (color >> 24) & 0xFF;
                            g += (color >> 16) & 0xFF;
                            b += (color >> 8) & 0xFF;
                            count++;
                        }
                    }

                    if (count > 0) {
                        cellColors.push({ r: r / count, g: g / count, b: b / count });
                    }
                }
            }

            // 计算网格间颜色的方差
            if (cellColors.length > 1) {
                let avgR = 0, avgG = 0, avgB = 0;
                cellColors.forEach(c => {
                    avgR += c.r;
                    avgG += c.g;
                    avgB += c.b;
                });
                avgR /= cellColors.length;
                avgG /= cellColors.length;
                avgB /= cellColors.length;

                let variance = 0;
                cellColors.forEach(c => {
                    variance += Math.pow(c.r - avgR, 2) + Math.pow(c.g - avgG, 2) + Math.pow(c.b - avgB, 2);
                });
                variance /= cellColors.length;

                // 方差越大，说明颜色分布越不均匀（作品图特征）
                if (variance > 5000) {
                    colorDistributionScore = 2000; // 颜色分布很不均匀，奖励
                } else if (variance > 3000) {
                    colorDistributionScore = 1500;
                } else if (variance > 1500) {
                    colorDistributionScore = 1000;
                } else if (variance > 800) {
                    colorDistributionScore = 500;
                } else {
                    colorDistributionScore = -1000; // 颜色分布太均匀，惩罚（背景图特征）
                }

                // 🎯 关键优化：位置>95%时，分布检测权重降低70%（作品图高概率区域）
                if (position > 0.95) {
                    colorDistributionScore = Math.round(colorDistributionScore * 0.3);
                }

                score += colorDistributionScore;
            }
        } catch (distributionError) {
            console.warn('颜色分布检测失败:', distributionError.message);
        }

        // 输出详细的评分信息
        const superBonusText = positionInfo.superBonus ? `+超级${positionInfo.superBonus}` : '';
        console.log(`  [评分详情] 位置:${positionInfo.positionPercent}% (基础${positionInfo.baseScore}+连续${positionInfo.continuousScore}${superBonusText}=${positionInfo.totalPositionScore})`);
        console.log(`             颜色:${colorDiversity}种 色相:${hueVariety}/12 边缘:${(edgeDensity*100).toFixed(1)}% 边缘分:${edgeScore}`);
        console.log(`             重复性分:${patternRepetitionScore} 分布分:${colorDistributionScore}`);

    } catch (colorError) {
        console.warn('色彩分析失败:', colorError.message);
    }

    return score;
}

/**
 * 从提取的图片中选择最佳作品图
 * @param {Array} images 图片信息数组
 * @returns {object} 最佳图片信息
 */
async function selectBestImageFromExtracted(images) {
    if (!images || images.length === 0) {
        throw new Error('没有可选择的图片');
    }

    // 输出前3名候选图片的详细评分
    console.log('\n' + '='.repeat(80));
    console.log('📊 作品图识别 - 前3名候选图片详细评分');
    console.log('='.repeat(80));

    const topCount = Math.min(3, images.length);
    for (let i = 0; i < topCount; i++) {
        const img = images[i];
        console.log(`\n🏅 排名 #${i + 1}: ${img.filename}`);
        console.log(`   总分: ${img.score.toFixed(2)} 分`);
        console.log(`   文件大小: ${(img.fileSize / 1024).toFixed(2)} KB`);
        console.log(`   图片尺寸: ${img.width}x${img.height}px`);
        // 注意：extractImagesFromPPT 中使用的是 index 属性，不是 imageIndex
        if (img.index !== undefined) {
            // 从 extractImagesFromPPT 获取的图片，totalImages 是所有图片的数量
            const totalImages = img.totalImages || images.length;
            const position = (img.index / Math.max(totalImages - 1, 1)) * 100;
            console.log(`   位置索引: ${img.index + 1}/${totalImages} (${position.toFixed(1)}%)`);
        } else {
            console.log(`   位置索引: 未知`);
        }
    }

    // 已经按评分排序，直接返回第一张
    const bestImage = images[0];

    console.log('\n' + '='.repeat(80));
    console.log('✅ 最终选择:');
    console.log(`   文件名: ${bestImage.filename}`);
    console.log(`   评分: ${bestImage.score.toFixed(2)} 分`);
    if (bestImage.index !== undefined) {
        const totalImages = bestImage.totalImages || images.length;
        const position = (bestImage.index / Math.max(totalImages - 1, 1)) * 100;
        console.log(`   位置: ${position.toFixed(1)}% (${bestImage.index + 1}/${totalImages})`);
    }
    console.log('='.repeat(80));

    return bestImage;
}

/**
 * 上传图片到腾讯云COS
 * @param {string} filePath 本地文件路径
 * @param {object} item 文件信息对象（包含fs_id）
 * @param {string} imgType 图片类型 "thumb"(缩略图) 或 "long"(长图)
 * @param {number} retryCount 当前重试次数
 * @returns {Promise<string>} 返回图片的完整URL
 */
async function uploadImageToCOS(filePath, item, imgType = "thumb", retryCount = 0) {
    const maxRetries = 3;
    let uploadFilePath = filePath;
    let needCleanup = false;

    try {
        // 检查文件是否存在
        if (!fs.existsSync(filePath)) {
            console.error(`上传失败：文件不存在 ${filePath}`);
            throw new Error(`文件不存在: ${filePath}`);
        }

        // 根据图片类型进行压缩
        if (imgType === "long") {
            console.log("检测到长图，开始压缩...");
            uploadFilePath = await compressLongImage(filePath);
            needCleanup = true; // 标记需要清理压缩后的临时文件
        } else if (imgType === "thumb") {
            console.log("检测到缩略图，开始压缩...");
            uploadFilePath = await compressThumbnail(filePath);
            needCleanup = true; // 标记需要清理压缩后的临时文件
        }

        console.log(`开始上传图片到腾讯云 (尝试 ${retryCount + 1}/${maxRetries + 1}):`, uploadFilePath);

        return await new Promise((resolve, reject) => {
            const now = new Date();
            const year = now.getFullYear();
            const month = String(now.getMonth() + 1).padStart(2, '0'); // 格式化为两位数

            const cos = new COS({
                SecretId: "AKIDOcdeYI0f8jARXQ6XklkdN8oR1RdgaHmE",
                SecretKey: 'LTREpDOGyUnunW3OVO3Ztzub4JLAl9JK',
            });

            // 根据图片类型确定文件名（压缩后都为jpg格式）
            const fileName = imgType === "long" ? 'long.jpg' : 'thumb.jpg';

            // 构建上传路径：pptImages/{yearMonth}/{fs_id}/{fileName}
            const uploadKey = `pptImages/${year}${month}/${item.fs_id}/${fileName}`;

            console.log(`上传路径: ${uploadKey}`);

            // 压缩后都是JPEG格式
            const contentType = 'image/jpeg';

            cos.putObject({
                Bucket: 'ppt-1375951275',
                Region: 'ap-shanghai',
                Key: uploadKey,
                Body: fs.createReadStream(uploadFilePath),
                ContentLength: fs.statSync(uploadFilePath).size,
                ContentType: contentType,
                onProgress: function (progressData) {
                    const percent = (progressData.percent * 100).toFixed(2);
                    if (percent % 10 === 0 || progressData.percent === 1) {
                        console.log(`上传进度: ${percent}%`);
                    }
                },
            }, async function (err, data) {
                // 上传完成后清理压缩的临时文件
                if (needCleanup && fs.existsSync(uploadFilePath)) {
                    try {
                        fs.unlinkSync(uploadFilePath);
                        console.log(`已清理压缩临时文件: ${uploadFilePath}`);
                    } catch (cleanupError) {
                        console.warn(`清理临时文件失败: ${cleanupError.message}`);
                    }
                }

                if (err) {
                    console.error(`上传失败 (尝试 ${retryCount + 1}):`, err);

                    // 如果是网络错误且还有重试次数，则重试
                    if (retryCount < maxRetries && (
                        err.message?.includes('socket hang up') ||
                        err.message?.includes('timeout') ||
                        err.message?.includes('ECONNRESET') ||
                        err.code === 'ENOTFOUND' ||
                        err.code === 'ECONNREFUSED'
                    )) {
                        console.log(`网络错误，将在3秒后重试...`);
                        setTimeout(async () => {
                            try {
                                const result = await uploadImageToCOS(filePath, item, imgType, retryCount + 1);
                                resolve(result);
                            } catch (retryError) {
                                reject(retryError);
                            }
                        }, 3000);
                    } else {
                        // 超过重试次数或其他错误
                        reject(err);
                    }
                } else {
                    // 上传成功，返回完整的URL
                    const imageUrl = `https://ppt-1375951275.cos.ap-shanghai.myqcloud.com/${uploadKey}`;
                    console.log(`图片上传成功:`, imageUrl);
                    resolve(imageUrl);
                }
            });
        });
    } catch (error) {
        // 如果出现错误，确保清理临时文件
        if (needCleanup && uploadFilePath !== filePath && fs.existsSync(uploadFilePath)) {
            try {
                fs.unlinkSync(uploadFilePath);
                console.log(`错误清理：已删除压缩临时文件`);
            } catch (cleanupError) {
                console.warn(`错误清理临时文件失败: ${cleanupError.message}`);
            }
        }
        throw error;
    }
}
